Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.27.0</build.version>
<build.version>1.27.1</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_AOneBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,11 +64,11 @@ public AOneBlockPlaceholders(AOneBlock addon,
}

/**
* Get the user's owned island. Returns the island owned by the user, not a team
* island they may be visiting as a member. If the user owns more than one island,
* one is picked.
* Get the user's island. Prefers an island the user owns; if they own none,
* falls back to the team island they are a member of. If the user owns more
* than one island, one is picked.
* @param user user
* @return island owned by the user, or empty if they own none
* @return island owned by the user, or their team island, or empty if neither exists
*/
private Optional<Island> getUsersIsland(User user) {
// Get the active island for the user
Expand All@@ -78,8 +78,14 @@ private Optional<Island> getUsersIsland(User user) {
return Optional.of(i);
}

// Find an island the user actually owns (not just a team island they are visiting)
return addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
// Prefer an island the user actually owns (not just a team island they are a member of)
Optional<Island> owned = addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
if (owned.isPresent()) {
return owned;
}

// Fall back to the team island the user is a member of, if any
return Optional.ofNullable(i);
}

public String getPhaseBlocksNames(User user) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,9 +25,11 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.BrushableBlock;
import org.bukkit.block.Chest;
import org.bukkit.block.data.Brushable;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
Expand DownExpand Up@@ -140,7 +142,24 @@ private record BrushSession(BukkitTask task, Block block) {}
}

private static final Random RAND = new Random();


/**
* Multiface plants, mapped to the block they are grown on when the magic block has nothing
* for them to attach to. Placed with their default block data these plants have no face set,
* a state that vanilla deletes at the next block update and that bone meal cannot spread from.
*/
private static final Map<Material, Material> MULTIFACE_SUPPORT = Map.of(
Material.GLOW_LICHEN, Material.MOSS_BLOCK,
Material.SCULK_VEIN, Material.SCULK,
Material.RESIN_CLUMP, Material.STONE,
Material.VINE, Material.MOSS_BLOCK);

/**
* Directions tried, in order, when a multiface plant has to be given a block to grow on.
*/
private static final List<BlockFace> MULTIFACE_OFFSETS = List.of(BlockFace.UP, BlockFace.NORTH,
BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.DOWN);

/**
* Constructs the BlockListener.
* @param addon - The AOneBlock addon instance.
Expand DownExpand Up@@ -710,6 +729,10 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
return;
}
Material type = nextBlock.getMaterial();
if (MULTIFACE_SUPPORT.containsKey(type)) {
spawnMultifaceBlock(block, type);
return;
}
block.setType(type, false);
if (type.equals(Material.CHEST) && nextBlock.getChest() != null) {
fillChest(nextBlock, block);
Expand All@@ -720,6 +743,63 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
}
}

/**
* Spawns a multiface plant - glow lichen, sculk vein, resin clump or vines.
* <p>
* These blocks are a film on the face of a neighboring block, not a cube. Placing one with
* {@code setType} gives it its default block data, which has no face set at all. The client
* draws that state on all six sides, so it looks fine, but the server treats it as
* unsupported: the next block update deletes it, and bone meal has no face to spread from.
* <p>
* So attach it to whatever solid neighbors the magic block already has. If it is floating in
* mid-air there is nothing to cling to - and nothing for bone meal to spread onto either - so
* the magic block becomes the plant's support block and the plant grows on the first free
* side of it.
*
* @param block The magic block being replaced.
* @param type The multiface material, e.g. {@link Material#GLOW_LICHEN}.
*/
private void spawnMultifaceBlock(@NonNull Block block, @NonNull Material type) {
if (!(type.createBlockData() instanceof MultipleFacing plant)) {
// Not a multiface block on this server version, so place it as-is
block.setType(type, false);
return;
}
boolean attached = false;
for (BlockFace face : plant.getAllowedFaces()) {
if (canAttachTo(block.getRelative(face), face.getOppositeFace())) {
plant.setFace(face, true);
attached = true;
}
}
if (attached) {
block.setBlockData(plant, false);
return;
}
// Nothing to grow on, so grow the plant a block to live on
block.setType(MULTIFACE_SUPPORT.get(type), false);
for (BlockFace offset : MULTIFACE_OFFSETS) {
BlockFace face = offset.getOppositeFace();
Block target = block.getRelative(offset);
if (plant.getAllowedFaces().contains(face) && target.getType().isAir()) {
plant.setFace(face, true);
target.setBlockData(plant, false);
return;
}
}
}

/**
* Checks whether a multiface plant can attach itself to the given face of a block.
*
* @param block The neighboring block.
* @param face The face of that block the plant would sit on.
* @return {@code true} if that face is a full, solid face.
*/
private boolean canAttachTo(@NonNull Block block, @NonNull BlockFace face) {
return !block.getType().isAir() && block.getBlockData().isFaceSturdy(face, BlockSupport.FULL);
}

/**
* Sets a leaves block to persistent so it does not decay.
* @param block The leaves block.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ public BossBarListener(AOneBlock addon) {

@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBreakBlockEvent(MagicBlockEvent e) {
if (e.getPlayerUUID() == null) {
return;
}
// Update boss bar
tryToShowBossBar(e.getPlayerUUID(), e.getIsland());
tryToShowActionBar(e.getPlayerUUID(), e.getIsland());
Expand Down
130 changes: 64 additions & 66 deletions src/main/resources/locales/cs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,132 +3,130 @@ protection:
MAGIC_BLOCK:
name: Ochrana Kouzelného Bloku
description: |-
&b Hodnost, která může rozbít
&b kouzelný blok, pokud
&b dokáže rozbíjet bloky.
hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!"
<aqua>Hodnost, která může rozbít
kouzelný blok, pokud
dokáže rozbíjet bloky.</aqua>
hint: "<red>Vaše hodnost nemůže rozbít kouzelný blok!</red>"
START_SAFETY:
name: Počáteční Bezpečnost
description: |-
&b Zabrání novým hráčům
&b v pohybu po dobu 1 minuty,
&b aby nespadli.
hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!"
free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!"
<aqua>Zabrání novým hráčům
v pohybu po dobu 1 minuty,
aby nespadli.</aqua>
hint: "<red>Pohyb zablokován kvůli bezpečnosti na [number] sekund!</red>"
free-to-move: "<green>Můžete se volně pohybovat. Buďte opatrní!</green>"
ONEBLOCK_BOSSBAR:
name: Boss Bar
description: |-
&b Zobrazuje stavový panel
&b pro každou fázi.
name: Boss Bar
description: |-
<aqua>Zobrazuje stavový panel
pro každou fázi.</aqua>
ONEBLOCK_ACTIONBAR:
name: Action Bar
description: |-
&b Zobrazuje stav
&b pro každou fázi
&b v Action Baru.
<aqua>Zobrazuje stav
pro každou fázi
v Action Baru.</aqua>
aoneblock:
bossbar:
title: Bloky zbývající
status: '&a Fázové bloky & B [done] & d / & b [total]'
status: '<green>Fázové bloky & B [done] & d / & b [total]</green>'
color: RED
style: SEGMENTED_20
not-active: '&c Boss Bar není pro tento ostrov aktivní'
not-active: '<red>Boss Bar není pro tento ostrov aktivní</red>'
actionbar:
status: "&a Fáze: &b [phase-name] &d | &a Bloky: &b [done] &d / &b [total] &d | &a Postup: &b [percent-done]"
not-active: "&c Action Bar není pro tento ostrov aktivní"
status: "<green>Fáze: </green><aqua>[phase-name] </aqua><light_purple>| </light_purple><green>Bloky: </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total] </aqua><light_purple>| </light_purple><green>Postup: </green><aqua>[percent-done]</aqua>"
not-active: "<red>Action Bar není pro tento ostrov aktivní</red>"
commands:
admin:
setcount:
parameters: <name> <count> [lifetime]
description: nastavit počet bloků hráče
set: '&a počet [name] je nastaven na [number]'
set-lifetime: '&a [name] je nastaveno na [number]'
set: '<green>počet [name] je nastaven na [number]</green>'
set-lifetime: '<green>[name] je nastaveno na [number]</green>'
setchest:
parameters: <phase> <rarity>
description: dejte pohled na hrudník do fáze se specifikovanou vzácností
chest-is-empty: '&c Ten hrudník je prázdný, takže jej nelze přidat'
unknown-phase: '&c Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor'
unknown-rarity: '&c Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC'
look-at-chest: '&c Podívejte se na naplněnou hruď a nastavte ji'
only-single-chest: '&c Lze nastavit pouze jednotlivé bedny'
success: '&a Hrudník byl úspěšně přidán do fáze'
failure: '&c Hrudník nelze přidat do fáze! Chyby najdete na konzole'
chest-is-empty: '<red>Ten hrudník je prázdný, takže jej nelze přidat</red>'
unknown-phase: '<red>Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor</red>'
unknown-rarity: '<red>Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC</red>'
look-at-chest: '<red>Podívejte se na naplněnou hruď a nastavte ji</red>'
only-single-chest: '<red>Lze nastavit pouze jednotlivé bedny</red>'
success: '<green>Hrudník byl úspěšně přidán do fáze</green>'
failure: '<red>Hrudník nelze přidat do fáze! Chyby najdete na konzole</red>'
sanity:
parameters: <fáze>
description: zobrazí v konzoli kontrolu pravděpodobnosti fází
see-console: '&a Podívejte se do konzoly pro zprávu'
see-console: '<green>Podívejte se do konzoly pro zprávu</green>'
count:
description: zobrazit počet bloků a fázi
info: '&a Jste na bloku &b [number] ve fázi &a [name]'
info: '<green>Jste na bloku </green><aqua>[number] ve fázi </aqua><green>[name]</green>'
info:
count: >-
Ostrov &a je na bloku &b [number]&a ve fázi &b [name] &a. Počet doživotí
&b [lifetime] &a.
count: 'Ostrov <green>je na bloku </green><aqua>[number]</aqua><green> ve fázi </green><aqua>[name] </aqua><green>. Počet doživotí </green><aqua>[lifetime] </aqua><green>.</green>'
phases:
description: zobrazit seznam všech fází
title: '&2 Fáze OneBlock'
name-syntax: '&a [name]'
description-syntax: '&b [number] bloků'
title: '<dark_green>Fáze OneBlock</dark_green>'
name-syntax: '<green>[name]</green>'
description-syntax: '<aqua>[number] bloků</aqua>'
island:
bossbar:
description: přepíná fázový šéfový bar
status_on: '&b Bossbar se otočil &a zapnul'
status_off: '&b Bossbar se &c otočil'
status_on: '<aqua>Bossbar se otočil </aqua><green>zapnul</green>'
status_off: '<aqua>Bossbar se </aqua><red>otočil</red>'
actionbar:
description: přepíná action bar fáze
status_on: "&b Action Bar &a zapnut"
status_off: "&b Action Bar &c vypnut"
status_on: "<aqua>Action Bar </aqua><green>zapnut</green>"
status_off: "<aqua>Action Bar </aqua><red>vypnut</red>"
setcount:
parameters: <count>
description: nastavte počet bloků na dříve dokončenou hodnotu
set: '&a Počet nastaven na [number].'
too-high: '&c Maximálně můžeš nastavit [number]!'
set: '<green>Počet nastaven na [number].</green>'
too-high: '<red>Maximálně můžeš nastavit [number]!</red>'
respawn-block:
description: respawnuje magický blok v situacích, kdy zmizí
block-exist: '&a Blok existuje, nevyžadoval respawning. Označil jsem to za vás.'
block-respawned: '&a Blok byl znovu vytvořen.'
block-exist: '<green>Blok existuje, nevyžadoval respawning. Označil jsem to za vás.</green>'
block-respawned: '<green>Blok byl znovu vytvořen.</green>'
phase:
insufficient-level: Tvůj ostrov je na příliš nízké úrovni, musí být alespoň [number].
insufficient-funds: Nemáš dostatečné prostředky! Musíš mít alespoň [number].
insufficient-bank-balance: V Bance ostrova není dostatek financí! Je potřeba alespoň [number].
insufficient-permission: '&c Nemůžete pokračovat, dokud nezískáte oprávnění [name]!'
cooldown: '&c Další fáze bude dostupná za [number] sekund!'
insufficient-permission: '<red>Nemůžete pokračovat, dokud nezískáte oprávnění [name]!</red>'
cooldown: '<red>Další fáze bude dostupná za [number] sekund!</red>'
placeholders:
infinite: Nekonečný
my-island-phase-default: Neznámá
gui:
titles:
phases: '&0&l Jednoblokové fáze'
phases: '<black><bold>Jednoblokové fáze</bold></black>'
buttons:
previous:
name: '&f&l Předchozí stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Předchozí stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
next:
name: '&f&l Další stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Další stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
phase:
name: '&f&l [phase]'
name: '<white><bold>[phase]</bold></white>'
description: |-
[starting-block]
[biome]
[bank]
[economy]
[level]
[permission]
starting-block: '&7 Spustí se po rozbití bloků &e [number].'
biome: '&7 Biom: &e [biome]'
bank: '&7 Vyžaduje &e $[number] &7 na bankovním účtu.'
economy: '&7 Vyžaduje &e $[number] &7 v hráčském účtu.'
level: '&7 Vyžaduje &e [number] &7 úroveň ostrova.'
permission: '&7 Vyžaduje oprávnění `&e[permission]&7`.'
blocks-prefix: '&7 Bloků ve fázi -'
blocks: '&e [name], '
starting-block: '<gray>Spustí se po rozbití bloků </gray><yellow>[number].</yellow>'
biome: '<gray>Biom: </gray><yellow>[biome]</yellow>'
bank: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>na bankovním účtu.</gray>'
economy: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>v hráčském účtu.</gray>'
level: '<gray>Vyžaduje </gray><yellow>[number] </yellow><gray>úroveň ostrova.</gray>'
permission: '<gray>Vyžaduje oprávnění `</gray><yellow>[permission]</yellow><gray>`.</gray>'
blocks-prefix: '<gray>Bloků ve fázi -</gray>'
blocks: '<yellow>[name], </yellow>'
wrap-at: '50'
tips:
click-to-previous: '&e Klepnutím na &7 zobrazíte předchozí stránku.'
click-to-next: '&e Klepnutím na &7 zobrazíte další stránku.'
click-to-change: '&e Klikněte na &7 pro změnu.'
click-to-previous: '<yellow>Klepnutím na </yellow><gray>zobrazíte předchozí stránku.</gray>'
click-to-next: '<yellow>Klepnutím na </yellow><gray>zobrazíte další stránku.</gray>'
click-to-change: '<yellow>Klikněte na </yellow><gray>pro změnu.</gray>'
island:
starting-hologram: |-
&a Vítejte v AOneBlock
&e Prolomte tento blok
<green>Vítejte v AOneBlock
</green><yellow>Prolomte tento blok</yellow>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.27.0</build.version>
<build.version>1.27.1</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_AOneBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,11 +64,11 @@ public AOneBlockPlaceholders(AOneBlock addon,
}

/**
* Get the user's owned island. Returns the island owned by the user, not a team
* island they may be visiting as a member. If the user owns more than one island,
* one is picked.
* Get the user's island. Prefers an island the user owns; if they own none,
* falls back to the team island they are a member of. If the user owns more
* than one island, one is picked.
* @param user user
* @return island owned by the user, or empty if they own none
* @return island owned by the user, or their team island, or empty if neither exists
*/
private Optional<Island> getUsersIsland(User user) {
// Get the active island for the user
Expand All@@ -78,8 +78,14 @@ private Optional<Island> getUsersIsland(User user) {
return Optional.of(i);
}

// Find an island the user actually owns (not just a team island they are visiting)
return addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
// Prefer an island the user actually owns (not just a team island they are a member of)
Optional<Island> owned = addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
if (owned.isPresent()) {
return owned;
}

// Fall back to the team island the user is a member of, if any
return Optional.ofNullable(i);
}

public String getPhaseBlocksNames(User user) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,9 +25,11 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.BrushableBlock;
import org.bukkit.block.Chest;
import org.bukkit.block.data.Brushable;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
Expand DownExpand Up@@ -140,7 +142,24 @@ private record BrushSession(BukkitTask task, Block block) {}
}

private static final Random RAND = new Random();


/**
* Multiface plants, mapped to the block they are grown on when the magic block has nothing
* for them to attach to. Placed with their default block data these plants have no face set,
* a state that vanilla deletes at the next block update and that bone meal cannot spread from.
*/
private static final Map<Material, Material> MULTIFACE_SUPPORT = Map.of(
Material.GLOW_LICHEN, Material.MOSS_BLOCK,
Material.SCULK_VEIN, Material.SCULK,
Material.RESIN_CLUMP, Material.STONE,
Material.VINE, Material.MOSS_BLOCK);

/**
* Directions tried, in order, when a multiface plant has to be given a block to grow on.
*/
private static final List<BlockFace> MULTIFACE_OFFSETS = List.of(BlockFace.UP, BlockFace.NORTH,
BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.DOWN);

/**
* Constructs the BlockListener.
* @param addon - The AOneBlock addon instance.
Expand DownExpand Up@@ -710,6 +729,10 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
return;
}
Material type = nextBlock.getMaterial();
if (MULTIFACE_SUPPORT.containsKey(type)) {
spawnMultifaceBlock(block, type);
return;
}
block.setType(type, false);
if (type.equals(Material.CHEST) && nextBlock.getChest() != null) {
fillChest(nextBlock, block);
Expand All@@ -720,6 +743,63 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
}
}

/**
* Spawns a multiface plant - glow lichen, sculk vein, resin clump or vines.
* <p>
* These blocks are a film on the face of a neighboring block, not a cube. Placing one with
* {@code setType} gives it its default block data, which has no face set at all. The client
* draws that state on all six sides, so it looks fine, but the server treats it as
* unsupported: the next block update deletes it, and bone meal has no face to spread from.
* <p>
* So attach it to whatever solid neighbors the magic block already has. If it is floating in
* mid-air there is nothing to cling to - and nothing for bone meal to spread onto either - so
* the magic block becomes the plant's support block and the plant grows on the first free
* side of it.
*
* @param block The magic block being replaced.
* @param type The multiface material, e.g. {@link Material#GLOW_LICHEN}.
*/
private void spawnMultifaceBlock(@NonNull Block block, @NonNull Material type) {
if (!(type.createBlockData() instanceof MultipleFacing plant)) {
// Not a multiface block on this server version, so place it as-is
block.setType(type, false);
return;
}
boolean attached = false;
for (BlockFace face : plant.getAllowedFaces()) {
if (canAttachTo(block.getRelative(face), face.getOppositeFace())) {
plant.setFace(face, true);
attached = true;
}
}
if (attached) {
block.setBlockData(plant, false);
return;
}
// Nothing to grow on, so grow the plant a block to live on
block.setType(MULTIFACE_SUPPORT.get(type), false);
for (BlockFace offset : MULTIFACE_OFFSETS) {
BlockFace face = offset.getOppositeFace();
Block target = block.getRelative(offset);
if (plant.getAllowedFaces().contains(face) && target.getType().isAir()) {
plant.setFace(face, true);
target.setBlockData(plant, false);
return;
}
}
}

/**
* Checks whether a multiface plant can attach itself to the given face of a block.
*
* @param block The neighboring block.
* @param face The face of that block the plant would sit on.
* @return {@code true} if that face is a full, solid face.
*/
private boolean canAttachTo(@NonNull Block block, @NonNull BlockFace face) {
return !block.getType().isAir() && block.getBlockData().isFaceSturdy(face, BlockSupport.FULL);
}

/**
* Sets a leaves block to persistent so it does not decay.
* @param block The leaves block.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ public BossBarListener(AOneBlock addon) {

@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBreakBlockEvent(MagicBlockEvent e) {
if (e.getPlayerUUID() == null) {
return;
}
// Update boss bar
tryToShowBossBar(e.getPlayerUUID(), e.getIsland());
tryToShowActionBar(e.getPlayerUUID(), e.getIsland());
Expand Down
130 changes: 64 additions & 66 deletions src/main/resources/locales/cs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,132 +3,130 @@ protection:
MAGIC_BLOCK:
name: Ochrana Kouzelného Bloku
description: |-
&b Hodnost, která může rozbít
&b kouzelný blok, pokud
&b dokáže rozbíjet bloky.
hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!"
<aqua>Hodnost, která může rozbít
kouzelný blok, pokud
dokáže rozbíjet bloky.</aqua>
hint: "<red>Vaše hodnost nemůže rozbít kouzelný blok!</red>"
START_SAFETY:
name: Počáteční Bezpečnost
description: |-
&b Zabrání novým hráčům
&b v pohybu po dobu 1 minuty,
&b aby nespadli.
hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!"
free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!"
<aqua>Zabrání novým hráčům
v pohybu po dobu 1 minuty,
aby nespadli.</aqua>
hint: "<red>Pohyb zablokován kvůli bezpečnosti na [number] sekund!</red>"
free-to-move: "<green>Můžete se volně pohybovat. Buďte opatrní!</green>"
ONEBLOCK_BOSSBAR:
name: Boss Bar
description: |-
&b Zobrazuje stavový panel
&b pro každou fázi.
name: Boss Bar
description: |-
<aqua>Zobrazuje stavový panel
pro každou fázi.</aqua>
ONEBLOCK_ACTIONBAR:
name: Action Bar
description: |-
&b Zobrazuje stav
&b pro každou fázi
&b v Action Baru.
<aqua>Zobrazuje stav
pro každou fázi
v Action Baru.</aqua>
aoneblock:
bossbar:
title: Bloky zbývající
status: '&a Fázové bloky & B [done] & d / & b [total]'
status: '<green>Fázové bloky & B [done] & d / & b [total]</green>'
color: RED
style: SEGMENTED_20
not-active: '&c Boss Bar není pro tento ostrov aktivní'
not-active: '<red>Boss Bar není pro tento ostrov aktivní</red>'
actionbar:
status: "&a Fáze: &b [phase-name] &d | &a Bloky: &b [done] &d / &b [total] &d | &a Postup: &b [percent-done]"
not-active: "&c Action Bar není pro tento ostrov aktivní"
status: "<green>Fáze: </green><aqua>[phase-name] </aqua><light_purple>| </light_purple><green>Bloky: </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total] </aqua><light_purple>| </light_purple><green>Postup: </green><aqua>[percent-done]</aqua>"
not-active: "<red>Action Bar není pro tento ostrov aktivní</red>"
commands:
admin:
setcount:
parameters: <name> <count> [lifetime]
description: nastavit počet bloků hráče
set: '&a počet [name] je nastaven na [number]'
set-lifetime: '&a [name] je nastaveno na [number]'
set: '<green>počet [name] je nastaven na [number]</green>'
set-lifetime: '<green>[name] je nastaveno na [number]</green>'
setchest:
parameters: <phase> <rarity>
description: dejte pohled na hrudník do fáze se specifikovanou vzácností
chest-is-empty: '&c Ten hrudník je prázdný, takže jej nelze přidat'
unknown-phase: '&c Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor'
unknown-rarity: '&c Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC'
look-at-chest: '&c Podívejte se na naplněnou hruď a nastavte ji'
only-single-chest: '&c Lze nastavit pouze jednotlivé bedny'
success: '&a Hrudník byl úspěšně přidán do fáze'
failure: '&c Hrudník nelze přidat do fáze! Chyby najdete na konzole'
chest-is-empty: '<red>Ten hrudník je prázdný, takže jej nelze přidat</red>'
unknown-phase: '<red>Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor</red>'
unknown-rarity: '<red>Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC</red>'
look-at-chest: '<red>Podívejte se na naplněnou hruď a nastavte ji</red>'
only-single-chest: '<red>Lze nastavit pouze jednotlivé bedny</red>'
success: '<green>Hrudník byl úspěšně přidán do fáze</green>'
failure: '<red>Hrudník nelze přidat do fáze! Chyby najdete na konzole</red>'
sanity:
parameters: <fáze>
description: zobrazí v konzoli kontrolu pravděpodobnosti fází
see-console: '&a Podívejte se do konzoly pro zprávu'
see-console: '<green>Podívejte se do konzoly pro zprávu</green>'
count:
description: zobrazit počet bloků a fázi
info: '&a Jste na bloku &b [number] ve fázi &a [name]'
info: '<green>Jste na bloku </green><aqua>[number] ve fázi </aqua><green>[name]</green>'
info:
count: >-
Ostrov &a je na bloku &b [number]&a ve fázi &b [name] &a. Počet doživotí
&b [lifetime] &a.
count: 'Ostrov <green>je na bloku </green><aqua>[number]</aqua><green> ve fázi </green><aqua>[name] </aqua><green>. Počet doživotí </green><aqua>[lifetime] </aqua><green>.</green>'
phases:
description: zobrazit seznam všech fází
title: '&2 Fáze OneBlock'
name-syntax: '&a [name]'
description-syntax: '&b [number] bloků'
title: '<dark_green>Fáze OneBlock</dark_green>'
name-syntax: '<green>[name]</green>'
description-syntax: '<aqua>[number] bloků</aqua>'
island:
bossbar:
description: přepíná fázový šéfový bar
status_on: '&b Bossbar se otočil &a zapnul'
status_off: '&b Bossbar se &c otočil'
status_on: '<aqua>Bossbar se otočil </aqua><green>zapnul</green>'
status_off: '<aqua>Bossbar se </aqua><red>otočil</red>'
actionbar:
description: přepíná action bar fáze
status_on: "&b Action Bar &a zapnut"
status_off: "&b Action Bar &c vypnut"
status_on: "<aqua>Action Bar </aqua><green>zapnut</green>"
status_off: "<aqua>Action Bar </aqua><red>vypnut</red>"
setcount:
parameters: <count>
description: nastavte počet bloků na dříve dokončenou hodnotu
set: '&a Počet nastaven na [number].'
too-high: '&c Maximálně můžeš nastavit [number]!'
set: '<green>Počet nastaven na [number].</green>'
too-high: '<red>Maximálně můžeš nastavit [number]!</red>'
respawn-block:
description: respawnuje magický blok v situacích, kdy zmizí
block-exist: '&a Blok existuje, nevyžadoval respawning. Označil jsem to za vás.'
block-respawned: '&a Blok byl znovu vytvořen.'
block-exist: '<green>Blok existuje, nevyžadoval respawning. Označil jsem to za vás.</green>'
block-respawned: '<green>Blok byl znovu vytvořen.</green>'
phase:
insufficient-level: Tvůj ostrov je na příliš nízké úrovni, musí být alespoň [number].
insufficient-funds: Nemáš dostatečné prostředky! Musíš mít alespoň [number].
insufficient-bank-balance: V Bance ostrova není dostatek financí! Je potřeba alespoň [number].
insufficient-permission: '&c Nemůžete pokračovat, dokud nezískáte oprávnění [name]!'
cooldown: '&c Další fáze bude dostupná za [number] sekund!'
insufficient-permission: '<red>Nemůžete pokračovat, dokud nezískáte oprávnění [name]!</red>'
cooldown: '<red>Další fáze bude dostupná za [number] sekund!</red>'
placeholders:
infinite: Nekonečný
my-island-phase-default: Neznámá
gui:
titles:
phases: '&0&l Jednoblokové fáze'
phases: '<black><bold>Jednoblokové fáze</bold></black>'
buttons:
previous:
name: '&f&l Předchozí stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Předchozí stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
next:
name: '&f&l Další stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Další stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
phase:
name: '&f&l [phase]'
name: '<white><bold>[phase]</bold></white>'
description: |-
[starting-block]
[biome]
[bank]
[economy]
[level]
[permission]
starting-block: '&7 Spustí se po rozbití bloků &e [number].'
biome: '&7 Biom: &e [biome]'
bank: '&7 Vyžaduje &e $[number] &7 na bankovním účtu.'
economy: '&7 Vyžaduje &e $[number] &7 v hráčském účtu.'
level: '&7 Vyžaduje &e [number] &7 úroveň ostrova.'
permission: '&7 Vyžaduje oprávnění `&e[permission]&7`.'
blocks-prefix: '&7 Bloků ve fázi -'
blocks: '&e [name], '
starting-block: '<gray>Spustí se po rozbití bloků </gray><yellow>[number].</yellow>'
biome: '<gray>Biom: </gray><yellow>[biome]</yellow>'
bank: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>na bankovním účtu.</gray>'
economy: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>v hráčském účtu.</gray>'
level: '<gray>Vyžaduje </gray><yellow>[number] </yellow><gray>úroveň ostrova.</gray>'
permission: '<gray>Vyžaduje oprávnění `</gray><yellow>[permission]</yellow><gray>`.</gray>'
blocks-prefix: '<gray>Bloků ve fázi -</gray>'
blocks: '<yellow>[name], </yellow>'
wrap-at: '50'
tips:
click-to-previous: '&e Klepnutím na &7 zobrazíte předchozí stránku.'
click-to-next: '&e Klepnutím na &7 zobrazíte další stránku.'
click-to-change: '&e Klikněte na &7 pro změnu.'
click-to-previous: '<yellow>Klepnutím na </yellow><gray>zobrazíte předchozí stránku.</gray>'
click-to-next: '<yellow>Klepnutím na </yellow><gray>zobrazíte další stránku.</gray>'
click-to-change: '<yellow>Klikněte na </yellow><gray>pro změnu.</gray>'
island:
starting-hologram: |-
&a Vítejte v AOneBlock
&e Prolomte tento blok
<green>Vítejte v AOneBlock
</green><yellow>Prolomte tento blok</yellow>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.27.0</build.version>
<build.version>1.27.1</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_AOneBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,11 +64,11 @@ public AOneBlockPlaceholders(AOneBlock addon,
}

/**
* Get the user's owned island. Returns the island owned by the user, not a team
* island they may be visiting as a member. If the user owns more than one island,
* one is picked.
* Get the user's island. Prefers an island the user owns; if they own none,
* falls back to the team island they are a member of. If the user owns more
* than one island, one is picked.
* @param user user
* @return island owned by the user, or empty if they own none
* @return island owned by the user, or their team island, or empty if neither exists
*/
private Optional<Island> getUsersIsland(User user) {
// Get the active island for the user
Expand All@@ -78,8 +78,14 @@ private Optional<Island> getUsersIsland(User user) {
return Optional.of(i);
}

// Find an island the user actually owns (not just a team island they are visiting)
return addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
// Prefer an island the user actually owns (not just a team island they are a member of)
Optional<Island> owned = addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
if (owned.isPresent()) {
return owned;
}

// Fall back to the team island the user is a member of, if any
return Optional.ofNullable(i);
}

public String getPhaseBlocksNames(User user) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,9 +25,11 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.BrushableBlock;
import org.bukkit.block.Chest;
import org.bukkit.block.data.Brushable;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
Expand DownExpand Up@@ -140,7 +142,24 @@ private record BrushSession(BukkitTask task, Block block) {}
}

private static final Random RAND = new Random();


/**
* Multiface plants, mapped to the block they are grown on when the magic block has nothing
* for them to attach to. Placed with their default block data these plants have no face set,
* a state that vanilla deletes at the next block update and that bone meal cannot spread from.
*/
private static final Map<Material, Material> MULTIFACE_SUPPORT = Map.of(
Material.GLOW_LICHEN, Material.MOSS_BLOCK,
Material.SCULK_VEIN, Material.SCULK,
Material.RESIN_CLUMP, Material.STONE,
Material.VINE, Material.MOSS_BLOCK);

/**
* Directions tried, in order, when a multiface plant has to be given a block to grow on.
*/
private static final List<BlockFace> MULTIFACE_OFFSETS = List.of(BlockFace.UP, BlockFace.NORTH,
BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.DOWN);

/**
* Constructs the BlockListener.
* @param addon - The AOneBlock addon instance.
Expand DownExpand Up@@ -710,6 +729,10 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
return;
}
Material type = nextBlock.getMaterial();
if (MULTIFACE_SUPPORT.containsKey(type)) {
spawnMultifaceBlock(block, type);
return;
}
block.setType(type, false);
if (type.equals(Material.CHEST) && nextBlock.getChest() != null) {
fillChest(nextBlock, block);
Expand All@@ -720,6 +743,63 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
}
}

/**
* Spawns a multiface plant - glow lichen, sculk vein, resin clump or vines.
* <p>
* These blocks are a film on the face of a neighboring block, not a cube. Placing one with
* {@code setType} gives it its default block data, which has no face set at all. The client
* draws that state on all six sides, so it looks fine, but the server treats it as
* unsupported: the next block update deletes it, and bone meal has no face to spread from.
* <p>
* So attach it to whatever solid neighbors the magic block already has. If it is floating in
* mid-air there is nothing to cling to - and nothing for bone meal to spread onto either - so
* the magic block becomes the plant's support block and the plant grows on the first free
* side of it.
*
* @param block The magic block being replaced.
* @param type The multiface material, e.g. {@link Material#GLOW_LICHEN}.
*/
private void spawnMultifaceBlock(@NonNull Block block, @NonNull Material type) {
if (!(type.createBlockData() instanceof MultipleFacing plant)) {
// Not a multiface block on this server version, so place it as-is
block.setType(type, false);
return;
}
boolean attached = false;
for (BlockFace face : plant.getAllowedFaces()) {
if (canAttachTo(block.getRelative(face), face.getOppositeFace())) {
plant.setFace(face, true);
attached = true;
}
}
if (attached) {
block.setBlockData(plant, false);
return;
}
// Nothing to grow on, so grow the plant a block to live on
block.setType(MULTIFACE_SUPPORT.get(type), false);
for (BlockFace offset : MULTIFACE_OFFSETS) {
BlockFace face = offset.getOppositeFace();
Block target = block.getRelative(offset);
if (plant.getAllowedFaces().contains(face) && target.getType().isAir()) {
plant.setFace(face, true);
target.setBlockData(plant, false);
return;
}
}
}

/**
* Checks whether a multiface plant can attach itself to the given face of a block.
*
* @param block The neighboring block.
* @param face The face of that block the plant would sit on.
* @return {@code true} if that face is a full, solid face.
*/
private boolean canAttachTo(@NonNull Block block, @NonNull BlockFace face) {
return !block.getType().isAir() && block.getBlockData().isFaceSturdy(face, BlockSupport.FULL);
}

/**
* Sets a leaves block to persistent so it does not decay.
* @param block The leaves block.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ public BossBarListener(AOneBlock addon) {

@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBreakBlockEvent(MagicBlockEvent e) {
if (e.getPlayerUUID() == null) {
return;
}
// Update boss bar
tryToShowBossBar(e.getPlayerUUID(), e.getIsland());
tryToShowActionBar(e.getPlayerUUID(), e.getIsland());
Expand Down
130 changes: 64 additions & 66 deletions src/main/resources/locales/cs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,132 +3,130 @@ protection:
MAGIC_BLOCK:
name: Ochrana Kouzelného Bloku
description: |-
&b Hodnost, která může rozbít
&b kouzelný blok, pokud
&b dokáže rozbíjet bloky.
hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!"
<aqua>Hodnost, která může rozbít
kouzelný blok, pokud
dokáže rozbíjet bloky.</aqua>
hint: "<red>Vaše hodnost nemůže rozbít kouzelný blok!</red>"
START_SAFETY:
name: Počáteční Bezpečnost
description: |-
&b Zabrání novým hráčům
&b v pohybu po dobu 1 minuty,
&b aby nespadli.
hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!"
free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!"
<aqua>Zabrání novým hráčům
v pohybu po dobu 1 minuty,
aby nespadli.</aqua>
hint: "<red>Pohyb zablokován kvůli bezpečnosti na [number] sekund!</red>"
free-to-move: "<green>Můžete se volně pohybovat. Buďte opatrní!</green>"
ONEBLOCK_BOSSBAR:
name: Boss Bar
description: |-
&b Zobrazuje stavový panel
&b pro každou fázi.
name: Boss Bar
description: |-
<aqua>Zobrazuje stavový panel
pro každou fázi.</aqua>
ONEBLOCK_ACTIONBAR:
name: Action Bar
description: |-
&b Zobrazuje stav
&b pro každou fázi
&b v Action Baru.
<aqua>Zobrazuje stav
pro každou fázi
v Action Baru.</aqua>
aoneblock:
bossbar:
title: Bloky zbývající
status: '&a Fázové bloky & B [done] & d / & b [total]'
status: '<green>Fázové bloky & B [done] & d / & b [total]</green>'
color: RED
style: SEGMENTED_20
not-active: '&c Boss Bar není pro tento ostrov aktivní'
not-active: '<red>Boss Bar není pro tento ostrov aktivní</red>'
actionbar:
status: "&a Fáze: &b [phase-name] &d | &a Bloky: &b [done] &d / &b [total] &d | &a Postup: &b [percent-done]"
not-active: "&c Action Bar není pro tento ostrov aktivní"
status: "<green>Fáze: </green><aqua>[phase-name] </aqua><light_purple>| </light_purple><green>Bloky: </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total] </aqua><light_purple>| </light_purple><green>Postup: </green><aqua>[percent-done]</aqua>"
not-active: "<red>Action Bar není pro tento ostrov aktivní</red>"
commands:
admin:
setcount:
parameters: <name> <count> [lifetime]
description: nastavit počet bloků hráče
set: '&a počet [name] je nastaven na [number]'
set-lifetime: '&a [name] je nastaveno na [number]'
set: '<green>počet [name] je nastaven na [number]</green>'
set-lifetime: '<green>[name] je nastaveno na [number]</green>'
setchest:
parameters: <phase> <rarity>
description: dejte pohled na hrudník do fáze se specifikovanou vzácností
chest-is-empty: '&c Ten hrudník je prázdný, takže jej nelze přidat'
unknown-phase: '&c Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor'
unknown-rarity: '&c Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC'
look-at-chest: '&c Podívejte se na naplněnou hruď a nastavte ji'
only-single-chest: '&c Lze nastavit pouze jednotlivé bedny'
success: '&a Hrudník byl úspěšně přidán do fáze'
failure: '&c Hrudník nelze přidat do fáze! Chyby najdete na konzole'
chest-is-empty: '<red>Ten hrudník je prázdný, takže jej nelze přidat</red>'
unknown-phase: '<red>Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor</red>'
unknown-rarity: '<red>Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC</red>'
look-at-chest: '<red>Podívejte se na naplněnou hruď a nastavte ji</red>'
only-single-chest: '<red>Lze nastavit pouze jednotlivé bedny</red>'
success: '<green>Hrudník byl úspěšně přidán do fáze</green>'
failure: '<red>Hrudník nelze přidat do fáze! Chyby najdete na konzole</red>'
sanity:
parameters: <fáze>
description: zobrazí v konzoli kontrolu pravděpodobnosti fází
see-console: '&a Podívejte se do konzoly pro zprávu'
see-console: '<green>Podívejte se do konzoly pro zprávu</green>'
count:
description: zobrazit počet bloků a fázi
info: '&a Jste na bloku &b [number] ve fázi &a [name]'
info: '<green>Jste na bloku </green><aqua>[number] ve fázi </aqua><green>[name]</green>'
info:
count: >-
Ostrov &a je na bloku &b [number]&a ve fázi &b [name] &a. Počet doživotí
&b [lifetime] &a.
count: 'Ostrov <green>je na bloku </green><aqua>[number]</aqua><green> ve fázi </green><aqua>[name] </aqua><green>. Počet doživotí </green><aqua>[lifetime] </aqua><green>.</green>'
phases:
description: zobrazit seznam všech fází
title: '&2 Fáze OneBlock'
name-syntax: '&a [name]'
description-syntax: '&b [number] bloků'
title: '<dark_green>Fáze OneBlock</dark_green>'
name-syntax: '<green>[name]</green>'
description-syntax: '<aqua>[number] bloků</aqua>'
island:
bossbar:
description: přepíná fázový šéfový bar
status_on: '&b Bossbar se otočil &a zapnul'
status_off: '&b Bossbar se &c otočil'
status_on: '<aqua>Bossbar se otočil </aqua><green>zapnul</green>'
status_off: '<aqua>Bossbar se </aqua><red>otočil</red>'
actionbar:
description: přepíná action bar fáze
status_on: "&b Action Bar &a zapnut"
status_off: "&b Action Bar &c vypnut"
status_on: "<aqua>Action Bar </aqua><green>zapnut</green>"
status_off: "<aqua>Action Bar </aqua><red>vypnut</red>"
setcount:
parameters: <count>
description: nastavte počet bloků na dříve dokončenou hodnotu
set: '&a Počet nastaven na [number].'
too-high: '&c Maximálně můžeš nastavit [number]!'
set: '<green>Počet nastaven na [number].</green>'
too-high: '<red>Maximálně můžeš nastavit [number]!</red>'
respawn-block:
description: respawnuje magický blok v situacích, kdy zmizí
block-exist: '&a Blok existuje, nevyžadoval respawning. Označil jsem to za vás.'
block-respawned: '&a Blok byl znovu vytvořen.'
block-exist: '<green>Blok existuje, nevyžadoval respawning. Označil jsem to za vás.</green>'
block-respawned: '<green>Blok byl znovu vytvořen.</green>'
phase:
insufficient-level: Tvůj ostrov je na příliš nízké úrovni, musí být alespoň [number].
insufficient-funds: Nemáš dostatečné prostředky! Musíš mít alespoň [number].
insufficient-bank-balance: V Bance ostrova není dostatek financí! Je potřeba alespoň [number].
insufficient-permission: '&c Nemůžete pokračovat, dokud nezískáte oprávnění [name]!'
cooldown: '&c Další fáze bude dostupná za [number] sekund!'
insufficient-permission: '<red>Nemůžete pokračovat, dokud nezískáte oprávnění [name]!</red>'
cooldown: '<red>Další fáze bude dostupná za [number] sekund!</red>'
placeholders:
infinite: Nekonečný
my-island-phase-default: Neznámá
gui:
titles:
phases: '&0&l Jednoblokové fáze'
phases: '<black><bold>Jednoblokové fáze</bold></black>'
buttons:
previous:
name: '&f&l Předchozí stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Předchozí stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
next:
name: '&f&l Další stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Další stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
phase:
name: '&f&l [phase]'
name: '<white><bold>[phase]</bold></white>'
description: |-
[starting-block]
[biome]
[bank]
[economy]
[level]
[permission]
starting-block: '&7 Spustí se po rozbití bloků &e [number].'
biome: '&7 Biom: &e [biome]'
bank: '&7 Vyžaduje &e $[number] &7 na bankovním účtu.'
economy: '&7 Vyžaduje &e $[number] &7 v hráčském účtu.'
level: '&7 Vyžaduje &e [number] &7 úroveň ostrova.'
permission: '&7 Vyžaduje oprávnění `&e[permission]&7`.'
blocks-prefix: '&7 Bloků ve fázi -'
blocks: '&e [name], '
starting-block: '<gray>Spustí se po rozbití bloků </gray><yellow>[number].</yellow>'
biome: '<gray>Biom: </gray><yellow>[biome]</yellow>'
bank: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>na bankovním účtu.</gray>'
economy: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>v hráčském účtu.</gray>'
level: '<gray>Vyžaduje </gray><yellow>[number] </yellow><gray>úroveň ostrova.</gray>'
permission: '<gray>Vyžaduje oprávnění `</gray><yellow>[permission]</yellow><gray>`.</gray>'
blocks-prefix: '<gray>Bloků ve fázi -</gray>'
blocks: '<yellow>[name], </yellow>'
wrap-at: '50'
tips:
click-to-previous: '&e Klepnutím na &7 zobrazíte předchozí stránku.'
click-to-next: '&e Klepnutím na &7 zobrazíte další stránku.'
click-to-change: '&e Klikněte na &7 pro změnu.'
click-to-previous: '<yellow>Klepnutím na </yellow><gray>zobrazíte předchozí stránku.</gray>'
click-to-next: '<yellow>Klepnutím na </yellow><gray>zobrazíte další stránku.</gray>'
click-to-change: '<yellow>Klikněte na </yellow><gray>pro změnu.</gray>'
island:
starting-hologram: |-
&a Vítejte v AOneBlock
&e Prolomte tento blok
<green>Vítejte v AOneBlock
</green><yellow>Prolomte tento blok</yellow>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.27.0</build.version>
<build.version>1.27.1</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_AOneBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,11 +64,11 @@ public AOneBlockPlaceholders(AOneBlock addon,
}

/**
* Get the user's owned island. Returns the island owned by the user, not a team
* island they may be visiting as a member. If the user owns more than one island,
* one is picked.
* Get the user's island. Prefers an island the user owns; if they own none,
* falls back to the team island they are a member of. If the user owns more
* than one island, one is picked.
* @param user user
* @return island owned by the user, or empty if they own none
* @return island owned by the user, or their team island, or empty if neither exists
*/
private Optional<Island> getUsersIsland(User user) {
// Get the active island for the user
Expand All@@ -78,8 +78,14 @@ private Optional<Island> getUsersIsland(User user) {
return Optional.of(i);
}

// Find an island the user actually owns (not just a team island they are visiting)
return addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
// Prefer an island the user actually owns (not just a team island they are a member of)
Optional<Island> owned = addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
if (owned.isPresent()) {
return owned;
}

// Fall back to the team island the user is a member of, if any
return Optional.ofNullable(i);
}

public String getPhaseBlocksNames(User user) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,9 +25,11 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.BrushableBlock;
import org.bukkit.block.Chest;
import org.bukkit.block.data.Brushable;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
Expand DownExpand Up@@ -140,7 +142,24 @@ private record BrushSession(BukkitTask task, Block block) {}
}

private static final Random RAND = new Random();


/**
* Multiface plants, mapped to the block they are grown on when the magic block has nothing
* for them to attach to. Placed with their default block data these plants have no face set,
* a state that vanilla deletes at the next block update and that bone meal cannot spread from.
*/
private static final Map<Material, Material> MULTIFACE_SUPPORT = Map.of(
Material.GLOW_LICHEN, Material.MOSS_BLOCK,
Material.SCULK_VEIN, Material.SCULK,
Material.RESIN_CLUMP, Material.STONE,
Material.VINE, Material.MOSS_BLOCK);

/**
* Directions tried, in order, when a multiface plant has to be given a block to grow on.
*/
private static final List<BlockFace> MULTIFACE_OFFSETS = List.of(BlockFace.UP, BlockFace.NORTH,
BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.DOWN);

/**
* Constructs the BlockListener.
* @param addon - The AOneBlock addon instance.
Expand DownExpand Up@@ -710,6 +729,10 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
return;
}
Material type = nextBlock.getMaterial();
if (MULTIFACE_SUPPORT.containsKey(type)) {
spawnMultifaceBlock(block, type);
return;
}
block.setType(type, false);
if (type.equals(Material.CHEST) && nextBlock.getChest() != null) {
fillChest(nextBlock, block);
Expand All@@ -720,6 +743,63 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
}
}

/**
* Spawns a multiface plant - glow lichen, sculk vein, resin clump or vines.
* <p>
* These blocks are a film on the face of a neighboring block, not a cube. Placing one with
* {@code setType} gives it its default block data, which has no face set at all. The client
* draws that state on all six sides, so it looks fine, but the server treats it as
* unsupported: the next block update deletes it, and bone meal has no face to spread from.
* <p>
* So attach it to whatever solid neighbors the magic block already has. If it is floating in
* mid-air there is nothing to cling to - and nothing for bone meal to spread onto either - so
* the magic block becomes the plant's support block and the plant grows on the first free
* side of it.
*
* @param block The magic block being replaced.
* @param type The multiface material, e.g. {@link Material#GLOW_LICHEN}.
*/
private void spawnMultifaceBlock(@NonNull Block block, @NonNull Material type) {
if (!(type.createBlockData() instanceof MultipleFacing plant)) {
// Not a multiface block on this server version, so place it as-is
block.setType(type, false);
return;
}
boolean attached = false;
for (BlockFace face : plant.getAllowedFaces()) {
if (canAttachTo(block.getRelative(face), face.getOppositeFace())) {
plant.setFace(face, true);
attached = true;
}
}
if (attached) {
block.setBlockData(plant, false);
return;
}
// Nothing to grow on, so grow the plant a block to live on
block.setType(MULTIFACE_SUPPORT.get(type), false);
for (BlockFace offset : MULTIFACE_OFFSETS) {
BlockFace face = offset.getOppositeFace();
Block target = block.getRelative(offset);
if (plant.getAllowedFaces().contains(face) && target.getType().isAir()) {
plant.setFace(face, true);
target.setBlockData(plant, false);
return;
}
}
}

/**
* Checks whether a multiface plant can attach itself to the given face of a block.
*
* @param block The neighboring block.
* @param face The face of that block the plant would sit on.
* @return {@code true} if that face is a full, solid face.
*/
private boolean canAttachTo(@NonNull Block block, @NonNull BlockFace face) {
return !block.getType().isAir() && block.getBlockData().isFaceSturdy(face, BlockSupport.FULL);
}

/**
* Sets a leaves block to persistent so it does not decay.
* @param block The leaves block.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ public BossBarListener(AOneBlock addon) {

@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBreakBlockEvent(MagicBlockEvent e) {
if (e.getPlayerUUID() == null) {
return;
}
// Update boss bar
tryToShowBossBar(e.getPlayerUUID(), e.getIsland());
tryToShowActionBar(e.getPlayerUUID(), e.getIsland());
Expand Down
130 changes: 64 additions & 66 deletions src/main/resources/locales/cs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,132 +3,130 @@ protection:
MAGIC_BLOCK:
name: Ochrana Kouzelného Bloku
description: |-
&b Hodnost, která může rozbít
&b kouzelný blok, pokud
&b dokáže rozbíjet bloky.
hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!"
<aqua>Hodnost, která může rozbít
kouzelný blok, pokud
dokáže rozbíjet bloky.</aqua>
hint: "<red>Vaše hodnost nemůže rozbít kouzelný blok!</red>"
START_SAFETY:
name: Počáteční Bezpečnost
description: |-
&b Zabrání novým hráčům
&b v pohybu po dobu 1 minuty,
&b aby nespadli.
hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!"
free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!"
<aqua>Zabrání novým hráčům
v pohybu po dobu 1 minuty,
aby nespadli.</aqua>
hint: "<red>Pohyb zablokován kvůli bezpečnosti na [number] sekund!</red>"
free-to-move: "<green>Můžete se volně pohybovat. Buďte opatrní!</green>"
ONEBLOCK_BOSSBAR:
name: Boss Bar
description: |-
&b Zobrazuje stavový panel
&b pro každou fázi.
name: Boss Bar
description: |-
<aqua>Zobrazuje stavový panel
pro každou fázi.</aqua>
ONEBLOCK_ACTIONBAR:
name: Action Bar
description: |-
&b Zobrazuje stav
&b pro každou fázi
&b v Action Baru.
<aqua>Zobrazuje stav
pro každou fázi
v Action Baru.</aqua>
aoneblock:
bossbar:
title: Bloky zbývající
status: '&a Fázové bloky & B [done] & d / & b [total]'
status: '<green>Fázové bloky & B [done] & d / & b [total]</green>'
color: RED
style: SEGMENTED_20
not-active: '&c Boss Bar není pro tento ostrov aktivní'
not-active: '<red>Boss Bar není pro tento ostrov aktivní</red>'
actionbar:
status: "&a Fáze: &b [phase-name] &d | &a Bloky: &b [done] &d / &b [total] &d | &a Postup: &b [percent-done]"
not-active: "&c Action Bar není pro tento ostrov aktivní"
status: "<green>Fáze: </green><aqua>[phase-name] </aqua><light_purple>| </light_purple><green>Bloky: </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total] </aqua><light_purple>| </light_purple><green>Postup: </green><aqua>[percent-done]</aqua>"
not-active: "<red>Action Bar není pro tento ostrov aktivní</red>"
commands:
admin:
setcount:
parameters: <name> <count> [lifetime]
description: nastavit počet bloků hráče
set: '&a počet [name] je nastaven na [number]'
set-lifetime: '&a [name] je nastaveno na [number]'
set: '<green>počet [name] je nastaven na [number]</green>'
set-lifetime: '<green>[name] je nastaveno na [number]</green>'
setchest:
parameters: <phase> <rarity>
description: dejte pohled na hrudník do fáze se specifikovanou vzácností
chest-is-empty: '&c Ten hrudník je prázdný, takže jej nelze přidat'
unknown-phase: '&c Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor'
unknown-rarity: '&c Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC'
look-at-chest: '&c Podívejte se na naplněnou hruď a nastavte ji'
only-single-chest: '&c Lze nastavit pouze jednotlivé bedny'
success: '&a Hrudník byl úspěšně přidán do fáze'
failure: '&c Hrudník nelze přidat do fáze! Chyby najdete na konzole'
chest-is-empty: '<red>Ten hrudník je prázdný, takže jej nelze přidat</red>'
unknown-phase: '<red>Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor</red>'
unknown-rarity: '<red>Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC</red>'
look-at-chest: '<red>Podívejte se na naplněnou hruď a nastavte ji</red>'
only-single-chest: '<red>Lze nastavit pouze jednotlivé bedny</red>'
success: '<green>Hrudník byl úspěšně přidán do fáze</green>'
failure: '<red>Hrudník nelze přidat do fáze! Chyby najdete na konzole</red>'
sanity:
parameters: <fáze>
description: zobrazí v konzoli kontrolu pravděpodobnosti fází
see-console: '&a Podívejte se do konzoly pro zprávu'
see-console: '<green>Podívejte se do konzoly pro zprávu</green>'
count:
description: zobrazit počet bloků a fázi
info: '&a Jste na bloku &b [number] ve fázi &a [name]'
info: '<green>Jste na bloku </green><aqua>[number] ve fázi </aqua><green>[name]</green>'
info:
count: >-
Ostrov &a je na bloku &b [number]&a ve fázi &b [name] &a. Počet doživotí
&b [lifetime] &a.
count: 'Ostrov <green>je na bloku </green><aqua>[number]</aqua><green> ve fázi </green><aqua>[name] </aqua><green>. Počet doživotí </green><aqua>[lifetime] </aqua><green>.</green>'
phases:
description: zobrazit seznam všech fází
title: '&2 Fáze OneBlock'
name-syntax: '&a [name]'
description-syntax: '&b [number] bloků'
title: '<dark_green>Fáze OneBlock</dark_green>'
name-syntax: '<green>[name]</green>'
description-syntax: '<aqua>[number] bloků</aqua>'
island:
bossbar:
description: přepíná fázový šéfový bar
status_on: '&b Bossbar se otočil &a zapnul'
status_off: '&b Bossbar se &c otočil'
status_on: '<aqua>Bossbar se otočil </aqua><green>zapnul</green>'
status_off: '<aqua>Bossbar se </aqua><red>otočil</red>'
actionbar:
description: přepíná action bar fáze
status_on: "&b Action Bar &a zapnut"
status_off: "&b Action Bar &c vypnut"
status_on: "<aqua>Action Bar </aqua><green>zapnut</green>"
status_off: "<aqua>Action Bar </aqua><red>vypnut</red>"
setcount:
parameters: <count>
description: nastavte počet bloků na dříve dokončenou hodnotu
set: '&a Počet nastaven na [number].'
too-high: '&c Maximálně můžeš nastavit [number]!'
set: '<green>Počet nastaven na [number].</green>'
too-high: '<red>Maximálně můžeš nastavit [number]!</red>'
respawn-block:
description: respawnuje magický blok v situacích, kdy zmizí
block-exist: '&a Blok existuje, nevyžadoval respawning. Označil jsem to za vás.'
block-respawned: '&a Blok byl znovu vytvořen.'
block-exist: '<green>Blok existuje, nevyžadoval respawning. Označil jsem to za vás.</green>'
block-respawned: '<green>Blok byl znovu vytvořen.</green>'
phase:
insufficient-level: Tvůj ostrov je na příliš nízké úrovni, musí být alespoň [number].
insufficient-funds: Nemáš dostatečné prostředky! Musíš mít alespoň [number].
insufficient-bank-balance: V Bance ostrova není dostatek financí! Je potřeba alespoň [number].
insufficient-permission: '&c Nemůžete pokračovat, dokud nezískáte oprávnění [name]!'
cooldown: '&c Další fáze bude dostupná za [number] sekund!'
insufficient-permission: '<red>Nemůžete pokračovat, dokud nezískáte oprávnění [name]!</red>'
cooldown: '<red>Další fáze bude dostupná za [number] sekund!</red>'
placeholders:
infinite: Nekonečný
my-island-phase-default: Neznámá
gui:
titles:
phases: '&0&l Jednoblokové fáze'
phases: '<black><bold>Jednoblokové fáze</bold></black>'
buttons:
previous:
name: '&f&l Předchozí stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Předchozí stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
next:
name: '&f&l Další stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Další stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
phase:
name: '&f&l [phase]'
name: '<white><bold>[phase]</bold></white>'
description: |-
[starting-block]
[biome]
[bank]
[economy]
[level]
[permission]
starting-block: '&7 Spustí se po rozbití bloků &e [number].'
biome: '&7 Biom: &e [biome]'
bank: '&7 Vyžaduje &e $[number] &7 na bankovním účtu.'
economy: '&7 Vyžaduje &e $[number] &7 v hráčském účtu.'
level: '&7 Vyžaduje &e [number] &7 úroveň ostrova.'
permission: '&7 Vyžaduje oprávnění `&e[permission]&7`.'
blocks-prefix: '&7 Bloků ve fázi -'
blocks: '&e [name], '
starting-block: '<gray>Spustí se po rozbití bloků </gray><yellow>[number].</yellow>'
biome: '<gray>Biom: </gray><yellow>[biome]</yellow>'
bank: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>na bankovním účtu.</gray>'
economy: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>v hráčském účtu.</gray>'
level: '<gray>Vyžaduje </gray><yellow>[number] </yellow><gray>úroveň ostrova.</gray>'
permission: '<gray>Vyžaduje oprávnění `</gray><yellow>[permission]</yellow><gray>`.</gray>'
blocks-prefix: '<gray>Bloků ve fázi -</gray>'
blocks: '<yellow>[name], </yellow>'
wrap-at: '50'
tips:
click-to-previous: '&e Klepnutím na &7 zobrazíte předchozí stránku.'
click-to-next: '&e Klepnutím na &7 zobrazíte další stránku.'
click-to-change: '&e Klikněte na &7 pro změnu.'
click-to-previous: '<yellow>Klepnutím na </yellow><gray>zobrazíte předchozí stránku.</gray>'
click-to-next: '<yellow>Klepnutím na </yellow><gray>zobrazíte další stránku.</gray>'
click-to-change: '<yellow>Klikněte na </yellow><gray>pro změnu.</gray>'
island:
starting-hologram: |-
&a Vítejte v AOneBlock
&e Prolomte tento blok
<green>Vítejte v AOneBlock
</green><yellow>Prolomte tento blok</yellow>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.27.0</build.version>
<build.version>1.27.1</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_AOneBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,11 +64,11 @@ public AOneBlockPlaceholders(AOneBlock addon,
}

/**
* Get the user's owned island. Returns the island owned by the user, not a team
* island they may be visiting as a member. If the user owns more than one island,
* one is picked.
* Get the user's island. Prefers an island the user owns; if they own none,
* falls back to the team island they are a member of. If the user owns more
* than one island, one is picked.
* @param user user
* @return island owned by the user, or empty if they own none
* @return island owned by the user, or their team island, or empty if neither exists
*/
private Optional<Island> getUsersIsland(User user) {
// Get the active island for the user
Expand All@@ -78,8 +78,14 @@ private Optional<Island> getUsersIsland(User user) {
return Optional.of(i);
}

// Find an island the user actually owns (not just a team island they are visiting)
return addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
// Prefer an island the user actually owns (not just a team island they are a member of)
Optional<Island> owned = addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
if (owned.isPresent()) {
return owned;
}

// Fall back to the team island the user is a member of, if any
return Optional.ofNullable(i);
}

public String getPhaseBlocksNames(User user) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,9 +25,11 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.BrushableBlock;
import org.bukkit.block.Chest;
import org.bukkit.block.data.Brushable;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
Expand DownExpand Up@@ -140,7 +142,24 @@ private record BrushSession(BukkitTask task, Block block) {}
}

private static final Random RAND = new Random();


/**
* Multiface plants, mapped to the block they are grown on when the magic block has nothing
* for them to attach to. Placed with their default block data these plants have no face set,
* a state that vanilla deletes at the next block update and that bone meal cannot spread from.
*/
private static final Map<Material, Material> MULTIFACE_SUPPORT = Map.of(
Material.GLOW_LICHEN, Material.MOSS_BLOCK,
Material.SCULK_VEIN, Material.SCULK,
Material.RESIN_CLUMP, Material.STONE,
Material.VINE, Material.MOSS_BLOCK);

/**
* Directions tried, in order, when a multiface plant has to be given a block to grow on.
*/
private static final List<BlockFace> MULTIFACE_OFFSETS = List.of(BlockFace.UP, BlockFace.NORTH,
BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.DOWN);

/**
* Constructs the BlockListener.
* @param addon - The AOneBlock addon instance.
Expand DownExpand Up@@ -710,6 +729,10 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
return;
}
Material type = nextBlock.getMaterial();
if (MULTIFACE_SUPPORT.containsKey(type)) {
spawnMultifaceBlock(block, type);
return;
}
block.setType(type, false);
if (type.equals(Material.CHEST) && nextBlock.getChest() != null) {
fillChest(nextBlock, block);
Expand All@@ -720,6 +743,63 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
}
}

/**
* Spawns a multiface plant - glow lichen, sculk vein, resin clump or vines.
* <p>
* These blocks are a film on the face of a neighboring block, not a cube. Placing one with
* {@code setType} gives it its default block data, which has no face set at all. The client
* draws that state on all six sides, so it looks fine, but the server treats it as
* unsupported: the next block update deletes it, and bone meal has no face to spread from.
* <p>
* So attach it to whatever solid neighbors the magic block already has. If it is floating in
* mid-air there is nothing to cling to - and nothing for bone meal to spread onto either - so
* the magic block becomes the plant's support block and the plant grows on the first free
* side of it.
*
* @param block The magic block being replaced.
* @param type The multiface material, e.g. {@link Material#GLOW_LICHEN}.
*/
private void spawnMultifaceBlock(@NonNull Block block, @NonNull Material type) {
if (!(type.createBlockData() instanceof MultipleFacing plant)) {
// Not a multiface block on this server version, so place it as-is
block.setType(type, false);
return;
}
boolean attached = false;
for (BlockFace face : plant.getAllowedFaces()) {
if (canAttachTo(block.getRelative(face), face.getOppositeFace())) {
plant.setFace(face, true);
attached = true;
}
}
if (attached) {
block.setBlockData(plant, false);
return;
}
// Nothing to grow on, so grow the plant a block to live on
block.setType(MULTIFACE_SUPPORT.get(type), false);
for (BlockFace offset : MULTIFACE_OFFSETS) {
BlockFace face = offset.getOppositeFace();
Block target = block.getRelative(offset);
if (plant.getAllowedFaces().contains(face) && target.getType().isAir()) {
plant.setFace(face, true);
target.setBlockData(plant, false);
return;
}
}
}

/**
* Checks whether a multiface plant can attach itself to the given face of a block.
*
* @param block The neighboring block.
* @param face The face of that block the plant would sit on.
* @return {@code true} if that face is a full, solid face.
*/
private boolean canAttachTo(@NonNull Block block, @NonNull BlockFace face) {
return !block.getType().isAir() && block.getBlockData().isFaceSturdy(face, BlockSupport.FULL);
}

/**
* Sets a leaves block to persistent so it does not decay.
* @param block The leaves block.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ public BossBarListener(AOneBlock addon) {

@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBreakBlockEvent(MagicBlockEvent e) {
if (e.getPlayerUUID() == null) {
return;
}
// Update boss bar
tryToShowBossBar(e.getPlayerUUID(), e.getIsland());
tryToShowActionBar(e.getPlayerUUID(), e.getIsland());
Expand Down
130 changes: 64 additions & 66 deletions src/main/resources/locales/cs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,132 +3,130 @@ protection:
MAGIC_BLOCK:
name: Ochrana Kouzelného Bloku
description: |-
&b Hodnost, která může rozbít
&b kouzelný blok, pokud
&b dokáže rozbíjet bloky.
hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!"
<aqua>Hodnost, která může rozbít
kouzelný blok, pokud
dokáže rozbíjet bloky.</aqua>
hint: "<red>Vaše hodnost nemůže rozbít kouzelný blok!</red>"
START_SAFETY:
name: Počáteční Bezpečnost
description: |-
&b Zabrání novým hráčům
&b v pohybu po dobu 1 minuty,
&b aby nespadli.
hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!"
free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!"
<aqua>Zabrání novým hráčům
v pohybu po dobu 1 minuty,
aby nespadli.</aqua>
hint: "<red>Pohyb zablokován kvůli bezpečnosti na [number] sekund!</red>"
free-to-move: "<green>Můžete se volně pohybovat. Buďte opatrní!</green>"
ONEBLOCK_BOSSBAR:
name: Boss Bar
description: |-
&b Zobrazuje stavový panel
&b pro každou fázi.
name: Boss Bar
description: |-
<aqua>Zobrazuje stavový panel
pro každou fázi.</aqua>
ONEBLOCK_ACTIONBAR:
name: Action Bar
description: |-
&b Zobrazuje stav
&b pro každou fázi
&b v Action Baru.
<aqua>Zobrazuje stav
pro každou fázi
v Action Baru.</aqua>
aoneblock:
bossbar:
title: Bloky zbývající
status: '&a Fázové bloky & B [done] & d / & b [total]'
status: '<green>Fázové bloky & B [done] & d / & b [total]</green>'
color: RED
style: SEGMENTED_20
not-active: '&c Boss Bar není pro tento ostrov aktivní'
not-active: '<red>Boss Bar není pro tento ostrov aktivní</red>'
actionbar:
status: "&a Fáze: &b [phase-name] &d | &a Bloky: &b [done] &d / &b [total] &d | &a Postup: &b [percent-done]"
not-active: "&c Action Bar není pro tento ostrov aktivní"
status: "<green>Fáze: </green><aqua>[phase-name] </aqua><light_purple>| </light_purple><green>Bloky: </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total] </aqua><light_purple>| </light_purple><green>Postup: </green><aqua>[percent-done]</aqua>"
not-active: "<red>Action Bar není pro tento ostrov aktivní</red>"
commands:
admin:
setcount:
parameters: <name> <count> [lifetime]
description: nastavit počet bloků hráče
set: '&a počet [name] je nastaven na [number]'
set-lifetime: '&a [name] je nastaveno na [number]'
set: '<green>počet [name] je nastaven na [number]</green>'
set-lifetime: '<green>[name] je nastaveno na [number]</green>'
setchest:
parameters: <phase> <rarity>
description: dejte pohled na hrudník do fáze se specifikovanou vzácností
chest-is-empty: '&c Ten hrudník je prázdný, takže jej nelze přidat'
unknown-phase: '&c Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor'
unknown-rarity: '&c Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC'
look-at-chest: '&c Podívejte se na naplněnou hruď a nastavte ji'
only-single-chest: '&c Lze nastavit pouze jednotlivé bedny'
success: '&a Hrudník byl úspěšně přidán do fáze'
failure: '&c Hrudník nelze přidat do fáze! Chyby najdete na konzole'
chest-is-empty: '<red>Ten hrudník je prázdný, takže jej nelze přidat</red>'
unknown-phase: '<red>Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor</red>'
unknown-rarity: '<red>Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC</red>'
look-at-chest: '<red>Podívejte se na naplněnou hruď a nastavte ji</red>'
only-single-chest: '<red>Lze nastavit pouze jednotlivé bedny</red>'
success: '<green>Hrudník byl úspěšně přidán do fáze</green>'
failure: '<red>Hrudník nelze přidat do fáze! Chyby najdete na konzole</red>'
sanity:
parameters: <fáze>
description: zobrazí v konzoli kontrolu pravděpodobnosti fází
see-console: '&a Podívejte se do konzoly pro zprávu'
see-console: '<green>Podívejte se do konzoly pro zprávu</green>'
count:
description: zobrazit počet bloků a fázi
info: '&a Jste na bloku &b [number] ve fázi &a [name]'
info: '<green>Jste na bloku </green><aqua>[number] ve fázi </aqua><green>[name]</green>'
info:
count: >-
Ostrov &a je na bloku &b [number]&a ve fázi &b [name] &a. Počet doživotí
&b [lifetime] &a.
count: 'Ostrov <green>je na bloku </green><aqua>[number]</aqua><green> ve fázi </green><aqua>[name] </aqua><green>. Počet doživotí </green><aqua>[lifetime] </aqua><green>.</green>'
phases:
description: zobrazit seznam všech fází
title: '&2 Fáze OneBlock'
name-syntax: '&a [name]'
description-syntax: '&b [number] bloků'
title: '<dark_green>Fáze OneBlock</dark_green>'
name-syntax: '<green>[name]</green>'
description-syntax: '<aqua>[number] bloků</aqua>'
island:
bossbar:
description: přepíná fázový šéfový bar
status_on: '&b Bossbar se otočil &a zapnul'
status_off: '&b Bossbar se &c otočil'
status_on: '<aqua>Bossbar se otočil </aqua><green>zapnul</green>'
status_off: '<aqua>Bossbar se </aqua><red>otočil</red>'
actionbar:
description: přepíná action bar fáze
status_on: "&b Action Bar &a zapnut"
status_off: "&b Action Bar &c vypnut"
status_on: "<aqua>Action Bar </aqua><green>zapnut</green>"
status_off: "<aqua>Action Bar </aqua><red>vypnut</red>"
setcount:
parameters: <count>
description: nastavte počet bloků na dříve dokončenou hodnotu
set: '&a Počet nastaven na [number].'
too-high: '&c Maximálně můžeš nastavit [number]!'
set: '<green>Počet nastaven na [number].</green>'
too-high: '<red>Maximálně můžeš nastavit [number]!</red>'
respawn-block:
description: respawnuje magický blok v situacích, kdy zmizí
block-exist: '&a Blok existuje, nevyžadoval respawning. Označil jsem to za vás.'
block-respawned: '&a Blok byl znovu vytvořen.'
block-exist: '<green>Blok existuje, nevyžadoval respawning. Označil jsem to za vás.</green>'
block-respawned: '<green>Blok byl znovu vytvořen.</green>'
phase:
insufficient-level: Tvůj ostrov je na příliš nízké úrovni, musí být alespoň [number].
insufficient-funds: Nemáš dostatečné prostředky! Musíš mít alespoň [number].
insufficient-bank-balance: V Bance ostrova není dostatek financí! Je potřeba alespoň [number].
insufficient-permission: '&c Nemůžete pokračovat, dokud nezískáte oprávnění [name]!'
cooldown: '&c Další fáze bude dostupná za [number] sekund!'
insufficient-permission: '<red>Nemůžete pokračovat, dokud nezískáte oprávnění [name]!</red>'
cooldown: '<red>Další fáze bude dostupná za [number] sekund!</red>'
placeholders:
infinite: Nekonečný
my-island-phase-default: Neznámá
gui:
titles:
phases: '&0&l Jednoblokové fáze'
phases: '<black><bold>Jednoblokové fáze</bold></black>'
buttons:
previous:
name: '&f&l Předchozí stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Předchozí stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
next:
name: '&f&l Další stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Další stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
phase:
name: '&f&l [phase]'
name: '<white><bold>[phase]</bold></white>'
description: |-
[starting-block]
[biome]
[bank]
[economy]
[level]
[permission]
starting-block: '&7 Spustí se po rozbití bloků &e [number].'
biome: '&7 Biom: &e [biome]'
bank: '&7 Vyžaduje &e $[number] &7 na bankovním účtu.'
economy: '&7 Vyžaduje &e $[number] &7 v hráčském účtu.'
level: '&7 Vyžaduje &e [number] &7 úroveň ostrova.'
permission: '&7 Vyžaduje oprávnění `&e[permission]&7`.'
blocks-prefix: '&7 Bloků ve fázi -'
blocks: '&e [name], '
starting-block: '<gray>Spustí se po rozbití bloků </gray><yellow>[number].</yellow>'
biome: '<gray>Biom: </gray><yellow>[biome]</yellow>'
bank: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>na bankovním účtu.</gray>'
economy: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>v hráčském účtu.</gray>'
level: '<gray>Vyžaduje </gray><yellow>[number] </yellow><gray>úroveň ostrova.</gray>'
permission: '<gray>Vyžaduje oprávnění `</gray><yellow>[permission]</yellow><gray>`.</gray>'
blocks-prefix: '<gray>Bloků ve fázi -</gray>'
blocks: '<yellow>[name], </yellow>'
wrap-at: '50'
tips:
click-to-previous: '&e Klepnutím na &7 zobrazíte předchozí stránku.'
click-to-next: '&e Klepnutím na &7 zobrazíte další stránku.'
click-to-change: '&e Klikněte na &7 pro změnu.'
click-to-previous: '<yellow>Klepnutím na </yellow><gray>zobrazíte předchozí stránku.</gray>'
click-to-next: '<yellow>Klepnutím na </yellow><gray>zobrazíte další stránku.</gray>'
click-to-change: '<yellow>Klikněte na </yellow><gray>pro změnu.</gray>'
island:
starting-hologram: |-
&a Vítejte v AOneBlock
&e Prolomte tento blok
<green>Vítejte v AOneBlock
</green><yellow>Prolomte tento blok</yellow>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.27.0</build.version>
<build.version>1.27.1</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_AOneBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,11 +64,11 @@ public AOneBlockPlaceholders(AOneBlock addon,
}

/**
* Get the user's owned island. Returns the island owned by the user, not a team
* island they may be visiting as a member. If the user owns more than one island,
* one is picked.
* Get the user's island. Prefers an island the user owns; if they own none,
* falls back to the team island they are a member of. If the user owns more
* than one island, one is picked.
* @param user user
* @return island owned by the user, or empty if they own none
* @return island owned by the user, or their team island, or empty if neither exists
*/
private Optional<Island> getUsersIsland(User user) {
// Get the active island for the user
Expand All@@ -78,8 +78,14 @@ private Optional<Island> getUsersIsland(User user) {
return Optional.of(i);
}

// Find an island the user actually owns (not just a team island they are visiting)
return addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
// Prefer an island the user actually owns (not just a team island they are a member of)
Optional<Island> owned = addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
if (owned.isPresent()) {
return owned;
}

// Fall back to the team island the user is a member of, if any
return Optional.ofNullable(i);
}

public String getPhaseBlocksNames(User user) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,9 +25,11 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.BrushableBlock;
import org.bukkit.block.Chest;
import org.bukkit.block.data.Brushable;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
Expand DownExpand Up@@ -140,7 +142,24 @@ private record BrushSession(BukkitTask task, Block block) {}
}

private static final Random RAND = new Random();


/**
* Multiface plants, mapped to the block they are grown on when the magic block has nothing
* for them to attach to. Placed with their default block data these plants have no face set,
* a state that vanilla deletes at the next block update and that bone meal cannot spread from.
*/
private static final Map<Material, Material> MULTIFACE_SUPPORT = Map.of(
Material.GLOW_LICHEN, Material.MOSS_BLOCK,
Material.SCULK_VEIN, Material.SCULK,
Material.RESIN_CLUMP, Material.STONE,
Material.VINE, Material.MOSS_BLOCK);

/**
* Directions tried, in order, when a multiface plant has to be given a block to grow on.
*/
private static final List<BlockFace> MULTIFACE_OFFSETS = List.of(BlockFace.UP, BlockFace.NORTH,
BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.DOWN);

/**
* Constructs the BlockListener.
* @param addon - The AOneBlock addon instance.
Expand DownExpand Up@@ -710,6 +729,10 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
return;
}
Material type = nextBlock.getMaterial();
if (MULTIFACE_SUPPORT.containsKey(type)) {
spawnMultifaceBlock(block, type);
return;
}
block.setType(type, false);
if (type.equals(Material.CHEST) && nextBlock.getChest() != null) {
fillChest(nextBlock, block);
Expand All@@ -720,6 +743,63 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
}
}

/**
* Spawns a multiface plant - glow lichen, sculk vein, resin clump or vines.
* <p>
* These blocks are a film on the face of a neighboring block, not a cube. Placing one with
* {@code setType} gives it its default block data, which has no face set at all. The client
* draws that state on all six sides, so it looks fine, but the server treats it as
* unsupported: the next block update deletes it, and bone meal has no face to spread from.
* <p>
* So attach it to whatever solid neighbors the magic block already has. If it is floating in
* mid-air there is nothing to cling to - and nothing for bone meal to spread onto either - so
* the magic block becomes the plant's support block and the plant grows on the first free
* side of it.
*
* @param block The magic block being replaced.
* @param type The multiface material, e.g. {@link Material#GLOW_LICHEN}.
*/
private void spawnMultifaceBlock(@NonNull Block block, @NonNull Material type) {
if (!(type.createBlockData() instanceof MultipleFacing plant)) {
// Not a multiface block on this server version, so place it as-is
block.setType(type, false);
return;
}
boolean attached = false;
for (BlockFace face : plant.getAllowedFaces()) {
if (canAttachTo(block.getRelative(face), face.getOppositeFace())) {
plant.setFace(face, true);
attached = true;
}
}
if (attached) {
block.setBlockData(plant, false);
return;
}
// Nothing to grow on, so grow the plant a block to live on
block.setType(MULTIFACE_SUPPORT.get(type), false);
for (BlockFace offset : MULTIFACE_OFFSETS) {
BlockFace face = offset.getOppositeFace();
Block target = block.getRelative(offset);
if (plant.getAllowedFaces().contains(face) && target.getType().isAir()) {
plant.setFace(face, true);
target.setBlockData(plant, false);
return;
}
}
}

/**
* Checks whether a multiface plant can attach itself to the given face of a block.
*
* @param block The neighboring block.
* @param face The face of that block the plant would sit on.
* @return {@code true} if that face is a full, solid face.
*/
private boolean canAttachTo(@NonNull Block block, @NonNull BlockFace face) {
return !block.getType().isAir() && block.getBlockData().isFaceSturdy(face, BlockSupport.FULL);
}

/**
* Sets a leaves block to persistent so it does not decay.
* @param block The leaves block.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ public BossBarListener(AOneBlock addon) {

@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBreakBlockEvent(MagicBlockEvent e) {
if (e.getPlayerUUID() == null) {
return;
}
// Update boss bar
tryToShowBossBar(e.getPlayerUUID(), e.getIsland());
tryToShowActionBar(e.getPlayerUUID(), e.getIsland());
Expand Down
130 changes: 64 additions & 66 deletions src/main/resources/locales/cs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,132 +3,130 @@ protection:
MAGIC_BLOCK:
name: Ochrana Kouzelného Bloku
description: |-
&b Hodnost, která může rozbít
&b kouzelný blok, pokud
&b dokáže rozbíjet bloky.
hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!"
<aqua>Hodnost, která může rozbít
kouzelný blok, pokud
dokáže rozbíjet bloky.</aqua>
hint: "<red>Vaše hodnost nemůže rozbít kouzelný blok!</red>"
START_SAFETY:
name: Počáteční Bezpečnost
description: |-
&b Zabrání novým hráčům
&b v pohybu po dobu 1 minuty,
&b aby nespadli.
hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!"
free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!"
<aqua>Zabrání novým hráčům
v pohybu po dobu 1 minuty,
aby nespadli.</aqua>
hint: "<red>Pohyb zablokován kvůli bezpečnosti na [number] sekund!</red>"
free-to-move: "<green>Můžete se volně pohybovat. Buďte opatrní!</green>"
ONEBLOCK_BOSSBAR:
name: Boss Bar
description: |-
&b Zobrazuje stavový panel
&b pro každou fázi.
name: Boss Bar
description: |-
<aqua>Zobrazuje stavový panel
pro každou fázi.</aqua>
ONEBLOCK_ACTIONBAR:
name: Action Bar
description: |-
&b Zobrazuje stav
&b pro každou fázi
&b v Action Baru.
<aqua>Zobrazuje stav
pro každou fázi
v Action Baru.</aqua>
aoneblock:
bossbar:
title: Bloky zbývající
status: '&a Fázové bloky & B [done] & d / & b [total]'
status: '<green>Fázové bloky & B [done] & d / & b [total]</green>'
color: RED
style: SEGMENTED_20
not-active: '&c Boss Bar není pro tento ostrov aktivní'
not-active: '<red>Boss Bar není pro tento ostrov aktivní</red>'
actionbar:
status: "&a Fáze: &b [phase-name] &d | &a Bloky: &b [done] &d / &b [total] &d | &a Postup: &b [percent-done]"
not-active: "&c Action Bar není pro tento ostrov aktivní"
status: "<green>Fáze: </green><aqua>[phase-name] </aqua><light_purple>| </light_purple><green>Bloky: </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total] </aqua><light_purple>| </light_purple><green>Postup: </green><aqua>[percent-done]</aqua>"
not-active: "<red>Action Bar není pro tento ostrov aktivní</red>"
commands:
admin:
setcount:
parameters: <name> <count> [lifetime]
description: nastavit počet bloků hráče
set: '&a počet [name] je nastaven na [number]'
set-lifetime: '&a [name] je nastaveno na [number]'
set: '<green>počet [name] je nastaven na [number]</green>'
set-lifetime: '<green>[name] je nastaveno na [number]</green>'
setchest:
parameters: <phase> <rarity>
description: dejte pohled na hrudník do fáze se specifikovanou vzácností
chest-is-empty: '&c Ten hrudník je prázdný, takže jej nelze přidat'
unknown-phase: '&c Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor'
unknown-rarity: '&c Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC'
look-at-chest: '&c Podívejte se na naplněnou hruď a nastavte ji'
only-single-chest: '&c Lze nastavit pouze jednotlivé bedny'
success: '&a Hrudník byl úspěšně přidán do fáze'
failure: '&c Hrudník nelze přidat do fáze! Chyby najdete na konzole'
chest-is-empty: '<red>Ten hrudník je prázdný, takže jej nelze přidat</red>'
unknown-phase: '<red>Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor</red>'
unknown-rarity: '<red>Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC</red>'
look-at-chest: '<red>Podívejte se na naplněnou hruď a nastavte ji</red>'
only-single-chest: '<red>Lze nastavit pouze jednotlivé bedny</red>'
success: '<green>Hrudník byl úspěšně přidán do fáze</green>'
failure: '<red>Hrudník nelze přidat do fáze! Chyby najdete na konzole</red>'
sanity:
parameters: <fáze>
description: zobrazí v konzoli kontrolu pravděpodobnosti fází
see-console: '&a Podívejte se do konzoly pro zprávu'
see-console: '<green>Podívejte se do konzoly pro zprávu</green>'
count:
description: zobrazit počet bloků a fázi
info: '&a Jste na bloku &b [number] ve fázi &a [name]'
info: '<green>Jste na bloku </green><aqua>[number] ve fázi </aqua><green>[name]</green>'
info:
count: >-
Ostrov &a je na bloku &b [number]&a ve fázi &b [name] &a. Počet doživotí
&b [lifetime] &a.
count: 'Ostrov <green>je na bloku </green><aqua>[number]</aqua><green> ve fázi </green><aqua>[name] </aqua><green>. Počet doživotí </green><aqua>[lifetime] </aqua><green>.</green>'
phases:
description: zobrazit seznam všech fází
title: '&2 Fáze OneBlock'
name-syntax: '&a [name]'
description-syntax: '&b [number] bloků'
title: '<dark_green>Fáze OneBlock</dark_green>'
name-syntax: '<green>[name]</green>'
description-syntax: '<aqua>[number] bloků</aqua>'
island:
bossbar:
description: přepíná fázový šéfový bar
status_on: '&b Bossbar se otočil &a zapnul'
status_off: '&b Bossbar se &c otočil'
status_on: '<aqua>Bossbar se otočil </aqua><green>zapnul</green>'
status_off: '<aqua>Bossbar se </aqua><red>otočil</red>'
actionbar:
description: přepíná action bar fáze
status_on: "&b Action Bar &a zapnut"
status_off: "&b Action Bar &c vypnut"
status_on: "<aqua>Action Bar </aqua><green>zapnut</green>"
status_off: "<aqua>Action Bar </aqua><red>vypnut</red>"
setcount:
parameters: <count>
description: nastavte počet bloků na dříve dokončenou hodnotu
set: '&a Počet nastaven na [number].'
too-high: '&c Maximálně můžeš nastavit [number]!'
set: '<green>Počet nastaven na [number].</green>'
too-high: '<red>Maximálně můžeš nastavit [number]!</red>'
respawn-block:
description: respawnuje magický blok v situacích, kdy zmizí
block-exist: '&a Blok existuje, nevyžadoval respawning. Označil jsem to za vás.'
block-respawned: '&a Blok byl znovu vytvořen.'
block-exist: '<green>Blok existuje, nevyžadoval respawning. Označil jsem to za vás.</green>'
block-respawned: '<green>Blok byl znovu vytvořen.</green>'
phase:
insufficient-level: Tvůj ostrov je na příliš nízké úrovni, musí být alespoň [number].
insufficient-funds: Nemáš dostatečné prostředky! Musíš mít alespoň [number].
insufficient-bank-balance: V Bance ostrova není dostatek financí! Je potřeba alespoň [number].
insufficient-permission: '&c Nemůžete pokračovat, dokud nezískáte oprávnění [name]!'
cooldown: '&c Další fáze bude dostupná za [number] sekund!'
insufficient-permission: '<red>Nemůžete pokračovat, dokud nezískáte oprávnění [name]!</red>'
cooldown: '<red>Další fáze bude dostupná za [number] sekund!</red>'
placeholders:
infinite: Nekonečný
my-island-phase-default: Neznámá
gui:
titles:
phases: '&0&l Jednoblokové fáze'
phases: '<black><bold>Jednoblokové fáze</bold></black>'
buttons:
previous:
name: '&f&l Předchozí stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Předchozí stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
next:
name: '&f&l Další stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Další stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
phase:
name: '&f&l [phase]'
name: '<white><bold>[phase]</bold></white>'
description: |-
[starting-block]
[biome]
[bank]
[economy]
[level]
[permission]
starting-block: '&7 Spustí se po rozbití bloků &e [number].'
biome: '&7 Biom: &e [biome]'
bank: '&7 Vyžaduje &e $[number] &7 na bankovním účtu.'
economy: '&7 Vyžaduje &e $[number] &7 v hráčském účtu.'
level: '&7 Vyžaduje &e [number] &7 úroveň ostrova.'
permission: '&7 Vyžaduje oprávnění `&e[permission]&7`.'
blocks-prefix: '&7 Bloků ve fázi -'
blocks: '&e [name], '
starting-block: '<gray>Spustí se po rozbití bloků </gray><yellow>[number].</yellow>'
biome: '<gray>Biom: </gray><yellow>[biome]</yellow>'
bank: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>na bankovním účtu.</gray>'
economy: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>v hráčském účtu.</gray>'
level: '<gray>Vyžaduje </gray><yellow>[number] </yellow><gray>úroveň ostrova.</gray>'
permission: '<gray>Vyžaduje oprávnění `</gray><yellow>[permission]</yellow><gray>`.</gray>'
blocks-prefix: '<gray>Bloků ve fázi -</gray>'
blocks: '<yellow>[name], </yellow>'
wrap-at: '50'
tips:
click-to-previous: '&e Klepnutím na &7 zobrazíte předchozí stránku.'
click-to-next: '&e Klepnutím na &7 zobrazíte další stránku.'
click-to-change: '&e Klikněte na &7 pro změnu.'
click-to-previous: '<yellow>Klepnutím na </yellow><gray>zobrazíte předchozí stránku.</gray>'
click-to-next: '<yellow>Klepnutím na </yellow><gray>zobrazíte další stránku.</gray>'
click-to-change: '<yellow>Klikněte na </yellow><gray>pro změnu.</gray>'
island:
starting-hologram: |-
&a Vítejte v AOneBlock
&e Prolomte tento blok
<green>Vítejte v AOneBlock
</green><yellow>Prolomte tento blok</yellow>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.27.0</build.version>
<build.version>1.27.1</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_AOneBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,11 +64,11 @@ public AOneBlockPlaceholders(AOneBlock addon,
}

/**
* Get the user's owned island. Returns the island owned by the user, not a team
* island they may be visiting as a member. If the user owns more than one island,
* one is picked.
* Get the user's island. Prefers an island the user owns; if they own none,
* falls back to the team island they are a member of. If the user owns more
* than one island, one is picked.
* @param user user
* @return island owned by the user, or empty if they own none
* @return island owned by the user, or their team island, or empty if neither exists
*/
private Optional<Island> getUsersIsland(User user) {
// Get the active island for the user
Expand All@@ -78,8 +78,14 @@ private Optional<Island> getUsersIsland(User user) {
return Optional.of(i);
}

// Find an island the user actually owns (not just a team island they are visiting)
return addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
// Prefer an island the user actually owns (not just a team island they are a member of)
Optional<Island> owned = addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
if (owned.isPresent()) {
return owned;
}

// Fall back to the team island the user is a member of, if any
return Optional.ofNullable(i);
}

public String getPhaseBlocksNames(User user) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,9 +25,11 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.BrushableBlock;
import org.bukkit.block.Chest;
import org.bukkit.block.data.Brushable;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
Expand DownExpand Up@@ -140,7 +142,24 @@ private record BrushSession(BukkitTask task, Block block) {}
}

private static final Random RAND = new Random();


/**
* Multiface plants, mapped to the block they are grown on when the magic block has nothing
* for them to attach to. Placed with their default block data these plants have no face set,
* a state that vanilla deletes at the next block update and that bone meal cannot spread from.
*/
private static final Map<Material, Material> MULTIFACE_SUPPORT = Map.of(
Material.GLOW_LICHEN, Material.MOSS_BLOCK,
Material.SCULK_VEIN, Material.SCULK,
Material.RESIN_CLUMP, Material.STONE,
Material.VINE, Material.MOSS_BLOCK);

/**
* Directions tried, in order, when a multiface plant has to be given a block to grow on.
*/
private static final List<BlockFace> MULTIFACE_OFFSETS = List.of(BlockFace.UP, BlockFace.NORTH,
BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.DOWN);

/**
* Constructs the BlockListener.
* @param addon - The AOneBlock addon instance.
Expand DownExpand Up@@ -710,6 +729,10 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
return;
}
Material type = nextBlock.getMaterial();
if (MULTIFACE_SUPPORT.containsKey(type)) {
spawnMultifaceBlock(block, type);
return;
}
block.setType(type, false);
if (type.equals(Material.CHEST) && nextBlock.getChest() != null) {
fillChest(nextBlock, block);
Expand All@@ -720,6 +743,63 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
}
}

/**
* Spawns a multiface plant - glow lichen, sculk vein, resin clump or vines.
* <p>
* These blocks are a film on the face of a neighboring block, not a cube. Placing one with
* {@code setType} gives it its default block data, which has no face set at all. The client
* draws that state on all six sides, so it looks fine, but the server treats it as
* unsupported: the next block update deletes it, and bone meal has no face to spread from.
* <p>
* So attach it to whatever solid neighbors the magic block already has. If it is floating in
* mid-air there is nothing to cling to - and nothing for bone meal to spread onto either - so
* the magic block becomes the plant's support block and the plant grows on the first free
* side of it.
*
* @param block The magic block being replaced.
* @param type The multiface material, e.g. {@link Material#GLOW_LICHEN}.
*/
private void spawnMultifaceBlock(@NonNull Block block, @NonNull Material type) {
if (!(type.createBlockData() instanceof MultipleFacing plant)) {
// Not a multiface block on this server version, so place it as-is
block.setType(type, false);
return;
}
boolean attached = false;
for (BlockFace face : plant.getAllowedFaces()) {
if (canAttachTo(block.getRelative(face), face.getOppositeFace())) {
plant.setFace(face, true);
attached = true;
}
}
if (attached) {
block.setBlockData(plant, false);
return;
}
// Nothing to grow on, so grow the plant a block to live on
block.setType(MULTIFACE_SUPPORT.get(type), false);
for (BlockFace offset : MULTIFACE_OFFSETS) {
BlockFace face = offset.getOppositeFace();
Block target = block.getRelative(offset);
if (plant.getAllowedFaces().contains(face) && target.getType().isAir()) {
plant.setFace(face, true);
target.setBlockData(plant, false);
return;
}
}
}

/**
* Checks whether a multiface plant can attach itself to the given face of a block.
*
* @param block The neighboring block.
* @param face The face of that block the plant would sit on.
* @return {@code true} if that face is a full, solid face.
*/
private boolean canAttachTo(@NonNull Block block, @NonNull BlockFace face) {
return !block.getType().isAir() && block.getBlockData().isFaceSturdy(face, BlockSupport.FULL);
}

/**
* Sets a leaves block to persistent so it does not decay.
* @param block The leaves block.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ public BossBarListener(AOneBlock addon) {

@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBreakBlockEvent(MagicBlockEvent e) {
if (e.getPlayerUUID() == null) {
return;
}
// Update boss bar
tryToShowBossBar(e.getPlayerUUID(), e.getIsland());
tryToShowActionBar(e.getPlayerUUID(), e.getIsland());
Expand Down
130 changes: 64 additions & 66 deletions src/main/resources/locales/cs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,132 +3,130 @@ protection:
MAGIC_BLOCK:
name: Ochrana Kouzelného Bloku
description: |-
&b Hodnost, která může rozbít
&b kouzelný blok, pokud
&b dokáže rozbíjet bloky.
hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!"
<aqua>Hodnost, která může rozbít
kouzelný blok, pokud
dokáže rozbíjet bloky.</aqua>
hint: "<red>Vaše hodnost nemůže rozbít kouzelný blok!</red>"
START_SAFETY:
name: Počáteční Bezpečnost
description: |-
&b Zabrání novým hráčům
&b v pohybu po dobu 1 minuty,
&b aby nespadli.
hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!"
free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!"
<aqua>Zabrání novým hráčům
v pohybu po dobu 1 minuty,
aby nespadli.</aqua>
hint: "<red>Pohyb zablokován kvůli bezpečnosti na [number] sekund!</red>"
free-to-move: "<green>Můžete se volně pohybovat. Buďte opatrní!</green>"
ONEBLOCK_BOSSBAR:
name: Boss Bar
description: |-
&b Zobrazuje stavový panel
&b pro každou fázi.
name: Boss Bar
description: |-
<aqua>Zobrazuje stavový panel
pro každou fázi.</aqua>
ONEBLOCK_ACTIONBAR:
name: Action Bar
description: |-
&b Zobrazuje stav
&b pro každou fázi
&b v Action Baru.
<aqua>Zobrazuje stav
pro každou fázi
v Action Baru.</aqua>
aoneblock:
bossbar:
title: Bloky zbývající
status: '&a Fázové bloky & B [done] & d / & b [total]'
status: '<green>Fázové bloky & B [done] & d / & b [total]</green>'
color: RED
style: SEGMENTED_20
not-active: '&c Boss Bar není pro tento ostrov aktivní'
not-active: '<red>Boss Bar není pro tento ostrov aktivní</red>'
actionbar:
status: "&a Fáze: &b [phase-name] &d | &a Bloky: &b [done] &d / &b [total] &d | &a Postup: &b [percent-done]"
not-active: "&c Action Bar není pro tento ostrov aktivní"
status: "<green>Fáze: </green><aqua>[phase-name] </aqua><light_purple>| </light_purple><green>Bloky: </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total] </aqua><light_purple>| </light_purple><green>Postup: </green><aqua>[percent-done]</aqua>"
not-active: "<red>Action Bar není pro tento ostrov aktivní</red>"
commands:
admin:
setcount:
parameters: <name> <count> [lifetime]
description: nastavit počet bloků hráče
set: '&a počet [name] je nastaven na [number]'
set-lifetime: '&a [name] je nastaveno na [number]'
set: '<green>počet [name] je nastaven na [number]</green>'
set-lifetime: '<green>[name] je nastaveno na [number]</green>'
setchest:
parameters: <phase> <rarity>
description: dejte pohled na hrudník do fáze se specifikovanou vzácností
chest-is-empty: '&c Ten hrudník je prázdný, takže jej nelze přidat'
unknown-phase: '&c Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor'
unknown-rarity: '&c Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC'
look-at-chest: '&c Podívejte se na naplněnou hruď a nastavte ji'
only-single-chest: '&c Lze nastavit pouze jednotlivé bedny'
success: '&a Hrudník byl úspěšně přidán do fáze'
failure: '&c Hrudník nelze přidat do fáze! Chyby najdete na konzole'
chest-is-empty: '<red>Ten hrudník je prázdný, takže jej nelze přidat</red>'
unknown-phase: '<red>Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor</red>'
unknown-rarity: '<red>Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC</red>'
look-at-chest: '<red>Podívejte se na naplněnou hruď a nastavte ji</red>'
only-single-chest: '<red>Lze nastavit pouze jednotlivé bedny</red>'
success: '<green>Hrudník byl úspěšně přidán do fáze</green>'
failure: '<red>Hrudník nelze přidat do fáze! Chyby najdete na konzole</red>'
sanity:
parameters: <fáze>
description: zobrazí v konzoli kontrolu pravděpodobnosti fází
see-console: '&a Podívejte se do konzoly pro zprávu'
see-console: '<green>Podívejte se do konzoly pro zprávu</green>'
count:
description: zobrazit počet bloků a fázi
info: '&a Jste na bloku &b [number] ve fázi &a [name]'
info: '<green>Jste na bloku </green><aqua>[number] ve fázi </aqua><green>[name]</green>'
info:
count: >-
Ostrov &a je na bloku &b [number]&a ve fázi &b [name] &a. Počet doživotí
&b [lifetime] &a.
count: 'Ostrov <green>je na bloku </green><aqua>[number]</aqua><green> ve fázi </green><aqua>[name] </aqua><green>. Počet doživotí </green><aqua>[lifetime] </aqua><green>.</green>'
phases:
description: zobrazit seznam všech fází
title: '&2 Fáze OneBlock'
name-syntax: '&a [name]'
description-syntax: '&b [number] bloků'
title: '<dark_green>Fáze OneBlock</dark_green>'
name-syntax: '<green>[name]</green>'
description-syntax: '<aqua>[number] bloků</aqua>'
island:
bossbar:
description: přepíná fázový šéfový bar
status_on: '&b Bossbar se otočil &a zapnul'
status_off: '&b Bossbar se &c otočil'
status_on: '<aqua>Bossbar se otočil </aqua><green>zapnul</green>'
status_off: '<aqua>Bossbar se </aqua><red>otočil</red>'
actionbar:
description: přepíná action bar fáze
status_on: "&b Action Bar &a zapnut"
status_off: "&b Action Bar &c vypnut"
status_on: "<aqua>Action Bar </aqua><green>zapnut</green>"
status_off: "<aqua>Action Bar </aqua><red>vypnut</red>"
setcount:
parameters: <count>
description: nastavte počet bloků na dříve dokončenou hodnotu
set: '&a Počet nastaven na [number].'
too-high: '&c Maximálně můžeš nastavit [number]!'
set: '<green>Počet nastaven na [number].</green>'
too-high: '<red>Maximálně můžeš nastavit [number]!</red>'
respawn-block:
description: respawnuje magický blok v situacích, kdy zmizí
block-exist: '&a Blok existuje, nevyžadoval respawning. Označil jsem to za vás.'
block-respawned: '&a Blok byl znovu vytvořen.'
block-exist: '<green>Blok existuje, nevyžadoval respawning. Označil jsem to za vás.</green>'
block-respawned: '<green>Blok byl znovu vytvořen.</green>'
phase:
insufficient-level: Tvůj ostrov je na příliš nízké úrovni, musí být alespoň [number].
insufficient-funds: Nemáš dostatečné prostředky! Musíš mít alespoň [number].
insufficient-bank-balance: V Bance ostrova není dostatek financí! Je potřeba alespoň [number].
insufficient-permission: '&c Nemůžete pokračovat, dokud nezískáte oprávnění [name]!'
cooldown: '&c Další fáze bude dostupná za [number] sekund!'
insufficient-permission: '<red>Nemůžete pokračovat, dokud nezískáte oprávnění [name]!</red>'
cooldown: '<red>Další fáze bude dostupná za [number] sekund!</red>'
placeholders:
infinite: Nekonečný
my-island-phase-default: Neznámá
gui:
titles:
phases: '&0&l Jednoblokové fáze'
phases: '<black><bold>Jednoblokové fáze</bold></black>'
buttons:
previous:
name: '&f&l Předchozí stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Předchozí stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
next:
name: '&f&l Další stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Další stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
phase:
name: '&f&l [phase]'
name: '<white><bold>[phase]</bold></white>'
description: |-
[starting-block]
[biome]
[bank]
[economy]
[level]
[permission]
starting-block: '&7 Spustí se po rozbití bloků &e [number].'
biome: '&7 Biom: &e [biome]'
bank: '&7 Vyžaduje &e $[number] &7 na bankovním účtu.'
economy: '&7 Vyžaduje &e $[number] &7 v hráčském účtu.'
level: '&7 Vyžaduje &e [number] &7 úroveň ostrova.'
permission: '&7 Vyžaduje oprávnění `&e[permission]&7`.'
blocks-prefix: '&7 Bloků ve fázi -'
blocks: '&e [name], '
starting-block: '<gray>Spustí se po rozbití bloků </gray><yellow>[number].</yellow>'
biome: '<gray>Biom: </gray><yellow>[biome]</yellow>'
bank: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>na bankovním účtu.</gray>'
economy: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>v hráčském účtu.</gray>'
level: '<gray>Vyžaduje </gray><yellow>[number] </yellow><gray>úroveň ostrova.</gray>'
permission: '<gray>Vyžaduje oprávnění `</gray><yellow>[permission]</yellow><gray>`.</gray>'
blocks-prefix: '<gray>Bloků ve fázi -</gray>'
blocks: '<yellow>[name], </yellow>'
wrap-at: '50'
tips:
click-to-previous: '&e Klepnutím na &7 zobrazíte předchozí stránku.'
click-to-next: '&e Klepnutím na &7 zobrazíte další stránku.'
click-to-change: '&e Klikněte na &7 pro změnu.'
click-to-previous: '<yellow>Klepnutím na </yellow><gray>zobrazíte předchozí stránku.</gray>'
click-to-next: '<yellow>Klepnutím na </yellow><gray>zobrazíte další stránku.</gray>'
click-to-change: '<yellow>Klikněte na </yellow><gray>pro změnu.</gray>'
island:
starting-hologram: |-
&a Vítejte v AOneBlock
&e Prolomte tento blok
<green>Vítejte v AOneBlock
</green><yellow>Prolomte tento blok</yellow>
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.27.0</build.version>
<build.version>1.27.1</build.version>
<!-- SonarCloud -->
<sonar.projectKey>BentoBoxWorld_AOneBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,11 +64,11 @@ public AOneBlockPlaceholders(AOneBlock addon,
}

/**
* Get the user's owned island. Returns the island owned by the user, not a team
* island they may be visiting as a member. If the user owns more than one island,
* one is picked.
* Get the user's island. Prefers an island the user owns; if they own none,
* falls back to the team island they are a member of. If the user owns more
* than one island, one is picked.
* @param user user
* @return island owned by the user, or empty if they own none
* @return island owned by the user, or their team island, or empty if neither exists
*/
private Optional<Island> getUsersIsland(User user) {
// Get the active island for the user
Expand All@@ -78,8 +78,14 @@ private Optional<Island> getUsersIsland(User user) {
return Optional.of(i);
}

// Find an island the user actually owns (not just a team island they are visiting)
return addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
// Prefer an island the user actually owns (not just a team island they are a member of)
Optional<Island> owned = addon.getIslands().getOwnedIslands(addon.getOverWorld(), user).stream().findFirst();
if (owned.isPresent()) {
return owned;
}

// Fall back to the team island the user is a member of, if any
return Optional.ofNullable(i);
}

public String getPhaseBlocksNames(User user) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,9 +25,11 @@
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockSupport;
import org.bukkit.block.BrushableBlock;
import org.bukkit.block.Chest;
import org.bukkit.block.data.Brushable;
import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.Leaves;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
Expand DownExpand Up@@ -140,7 +142,24 @@ private record BrushSession(BukkitTask task, Block block) {}
}

private static final Random RAND = new Random();


/**
* Multiface plants, mapped to the block they are grown on when the magic block has nothing
* for them to attach to. Placed with their default block data these plants have no face set,
* a state that vanilla deletes at the next block update and that bone meal cannot spread from.
*/
private static final Map<Material, Material> MULTIFACE_SUPPORT = Map.of(
Material.GLOW_LICHEN, Material.MOSS_BLOCK,
Material.SCULK_VEIN, Material.SCULK,
Material.RESIN_CLUMP, Material.STONE,
Material.VINE, Material.MOSS_BLOCK);

/**
* Directions tried, in order, when a multiface plant has to be given a block to grow on.
*/
private static final List<BlockFace> MULTIFACE_OFFSETS = List.of(BlockFace.UP, BlockFace.NORTH,
BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.DOWN);

/**
* Constructs the BlockListener.
* @param addon - The AOneBlock addon instance.
Expand DownExpand Up@@ -710,6 +729,10 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
return;
}
Material type = nextBlock.getMaterial();
if (MULTIFACE_SUPPORT.containsKey(type)) {
spawnMultifaceBlock(block, type);
return;
}
block.setType(type, false);
if (type.equals(Material.CHEST) && nextBlock.getChest() != null) {
fillChest(nextBlock, block);
Expand All@@ -720,6 +743,63 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block)
}
}

/**
* Spawns a multiface plant - glow lichen, sculk vein, resin clump or vines.
* <p>
* These blocks are a film on the face of a neighboring block, not a cube. Placing one with
* {@code setType} gives it its default block data, which has no face set at all. The client
* draws that state on all six sides, so it looks fine, but the server treats it as
* unsupported: the next block update deletes it, and bone meal has no face to spread from.
* <p>
* So attach it to whatever solid neighbors the magic block already has. If it is floating in
* mid-air there is nothing to cling to - and nothing for bone meal to spread onto either - so
* the magic block becomes the plant's support block and the plant grows on the first free
* side of it.
*
* @param block The magic block being replaced.
* @param type The multiface material, e.g. {@link Material#GLOW_LICHEN}.
*/
private void spawnMultifaceBlock(@NonNull Block block, @NonNull Material type) {
if (!(type.createBlockData() instanceof MultipleFacing plant)) {
// Not a multiface block on this server version, so place it as-is
block.setType(type, false);
return;
}
boolean attached = false;
for (BlockFace face : plant.getAllowedFaces()) {
if (canAttachTo(block.getRelative(face), face.getOppositeFace())) {
plant.setFace(face, true);
attached = true;
}
}
if (attached) {
block.setBlockData(plant, false);
return;
}
// Nothing to grow on, so grow the plant a block to live on
block.setType(MULTIFACE_SUPPORT.get(type), false);
for (BlockFace offset : MULTIFACE_OFFSETS) {
BlockFace face = offset.getOppositeFace();
Block target = block.getRelative(offset);
if (plant.getAllowedFaces().contains(face) && target.getType().isAir()) {
plant.setFace(face, true);
target.setBlockData(plant, false);
return;
}
}
}

/**
* Checks whether a multiface plant can attach itself to the given face of a block.
*
* @param block The neighboring block.
* @param face The face of that block the plant would sit on.
* @return {@code true} if that face is a full, solid face.
*/
private boolean canAttachTo(@NonNull Block block, @NonNull BlockFace face) {
return !block.getType().isAir() && block.getBlockData().isFaceSturdy(face, BlockSupport.FULL);
}

/**
* Sets a leaves block to persistent so it does not decay.
* @param block The leaves block.
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,9 @@ public BossBarListener(AOneBlock addon) {

@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBreakBlockEvent(MagicBlockEvent e) {
if (e.getPlayerUUID() == null) {
return;
}
// Update boss bar
tryToShowBossBar(e.getPlayerUUID(), e.getIsland());
tryToShowActionBar(e.getPlayerUUID(), e.getIsland());
Expand Down
130 changes: 64 additions & 66 deletions src/main/resources/locales/cs.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,132 +3,130 @@ protection:
MAGIC_BLOCK:
name: Ochrana Kouzelného Bloku
description: |-
&b Hodnost, která může rozbít
&b kouzelný blok, pokud
&b dokáže rozbíjet bloky.
hint: "&c Vaše hodnost nemůže rozbít kouzelný blok!"
<aqua>Hodnost, která může rozbít
kouzelný blok, pokud
dokáže rozbíjet bloky.</aqua>
hint: "<red>Vaše hodnost nemůže rozbít kouzelný blok!</red>"
START_SAFETY:
name: Počáteční Bezpečnost
description: |-
&b Zabrání novým hráčům
&b v pohybu po dobu 1 minuty,
&b aby nespadli.
hint: "&c Pohyb zablokován kvůli bezpečnosti na [number] sekund!"
free-to-move: "&a Můžete se volně pohybovat. Buďte opatrní!"
<aqua>Zabrání novým hráčům
v pohybu po dobu 1 minuty,
aby nespadli.</aqua>
hint: "<red>Pohyb zablokován kvůli bezpečnosti na [number] sekund!</red>"
free-to-move: "<green>Můžete se volně pohybovat. Buďte opatrní!</green>"
ONEBLOCK_BOSSBAR:
name: Boss Bar
description: |-
&b Zobrazuje stavový panel
&b pro každou fázi.
name: Boss Bar
description: |-
<aqua>Zobrazuje stavový panel
pro každou fázi.</aqua>
ONEBLOCK_ACTIONBAR:
name: Action Bar
description: |-
&b Zobrazuje stav
&b pro každou fázi
&b v Action Baru.
<aqua>Zobrazuje stav
pro každou fázi
v Action Baru.</aqua>
aoneblock:
bossbar:
title: Bloky zbývající
status: '&a Fázové bloky & B [done] & d / & b [total]'
status: '<green>Fázové bloky & B [done] & d / & b [total]</green>'
color: RED
style: SEGMENTED_20
not-active: '&c Boss Bar není pro tento ostrov aktivní'
not-active: '<red>Boss Bar není pro tento ostrov aktivní</red>'
actionbar:
status: "&a Fáze: &b [phase-name] &d | &a Bloky: &b [done] &d / &b [total] &d | &a Postup: &b [percent-done]"
not-active: "&c Action Bar není pro tento ostrov aktivní"
status: "<green>Fáze: </green><aqua>[phase-name] </aqua><light_purple>| </light_purple><green>Bloky: </green><aqua>[done] </aqua><light_purple>/ </light_purple><aqua>[total] </aqua><light_purple>| </light_purple><green>Postup: </green><aqua>[percent-done]</aqua>"
not-active: "<red>Action Bar není pro tento ostrov aktivní</red>"
commands:
admin:
setcount:
parameters: <name> <count> [lifetime]
description: nastavit počet bloků hráče
set: '&a počet [name] je nastaven na [number]'
set-lifetime: '&a [name] je nastaveno na [number]'
set: '<green>počet [name] je nastaven na [number]</green>'
set-lifetime: '<green>[name] je nastaveno na [number]</green>'
setchest:
parameters: <phase> <rarity>
description: dejte pohled na hrudník do fáze se specifikovanou vzácností
chest-is-empty: '&c Ten hrudník je prázdný, takže jej nelze přidat'
unknown-phase: '&c Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor'
unknown-rarity: '&c Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC'
look-at-chest: '&c Podívejte se na naplněnou hruď a nastavte ji'
only-single-chest: '&c Lze nastavit pouze jednotlivé bedny'
success: '&a Hrudník byl úspěšně přidán do fáze'
failure: '&c Hrudník nelze přidat do fáze! Chyby najdete na konzole'
chest-is-empty: '<red>Ten hrudník je prázdný, takže jej nelze přidat</red>'
unknown-phase: '<red>Neznámá fáze. Chcete-li si je prohlédnout, použijte tabulátor</red>'
unknown-rarity: '<red>Neznámá vzácnost. Používejte COMMON, UNCOMMON, RARE nebo EPIC</red>'
look-at-chest: '<red>Podívejte se na naplněnou hruď a nastavte ji</red>'
only-single-chest: '<red>Lze nastavit pouze jednotlivé bedny</red>'
success: '<green>Hrudník byl úspěšně přidán do fáze</green>'
failure: '<red>Hrudník nelze přidat do fáze! Chyby najdete na konzole</red>'
sanity:
parameters: <fáze>
description: zobrazí v konzoli kontrolu pravděpodobnosti fází
see-console: '&a Podívejte se do konzoly pro zprávu'
see-console: '<green>Podívejte se do konzoly pro zprávu</green>'
count:
description: zobrazit počet bloků a fázi
info: '&a Jste na bloku &b [number] ve fázi &a [name]'
info: '<green>Jste na bloku </green><aqua>[number] ve fázi </aqua><green>[name]</green>'
info:
count: >-
Ostrov &a je na bloku &b [number]&a ve fázi &b [name] &a. Počet doživotí
&b [lifetime] &a.
count: 'Ostrov <green>je na bloku </green><aqua>[number]</aqua><green> ve fázi </green><aqua>[name] </aqua><green>. Počet doživotí </green><aqua>[lifetime] </aqua><green>.</green>'
phases:
description: zobrazit seznam všech fází
title: '&2 Fáze OneBlock'
name-syntax: '&a [name]'
description-syntax: '&b [number] bloků'
title: '<dark_green>Fáze OneBlock</dark_green>'
name-syntax: '<green>[name]</green>'
description-syntax: '<aqua>[number] bloků</aqua>'
island:
bossbar:
description: přepíná fázový šéfový bar
status_on: '&b Bossbar se otočil &a zapnul'
status_off: '&b Bossbar se &c otočil'
status_on: '<aqua>Bossbar se otočil </aqua><green>zapnul</green>'
status_off: '<aqua>Bossbar se </aqua><red>otočil</red>'
actionbar:
description: přepíná action bar fáze
status_on: "&b Action Bar &a zapnut"
status_off: "&b Action Bar &c vypnut"
status_on: "<aqua>Action Bar </aqua><green>zapnut</green>"
status_off: "<aqua>Action Bar </aqua><red>vypnut</red>"
setcount:
parameters: <count>
description: nastavte počet bloků na dříve dokončenou hodnotu
set: '&a Počet nastaven na [number].'
too-high: '&c Maximálně můžeš nastavit [number]!'
set: '<green>Počet nastaven na [number].</green>'
too-high: '<red>Maximálně můžeš nastavit [number]!</red>'
respawn-block:
description: respawnuje magický blok v situacích, kdy zmizí
block-exist: '&a Blok existuje, nevyžadoval respawning. Označil jsem to za vás.'
block-respawned: '&a Blok byl znovu vytvořen.'
block-exist: '<green>Blok existuje, nevyžadoval respawning. Označil jsem to za vás.</green>'
block-respawned: '<green>Blok byl znovu vytvořen.</green>'
phase:
insufficient-level: Tvůj ostrov je na příliš nízké úrovni, musí být alespoň [number].
insufficient-funds: Nemáš dostatečné prostředky! Musíš mít alespoň [number].
insufficient-bank-balance: V Bance ostrova není dostatek financí! Je potřeba alespoň [number].
insufficient-permission: '&c Nemůžete pokračovat, dokud nezískáte oprávnění [name]!'
cooldown: '&c Další fáze bude dostupná za [number] sekund!'
insufficient-permission: '<red>Nemůžete pokračovat, dokud nezískáte oprávnění [name]!</red>'
cooldown: '<red>Další fáze bude dostupná za [number] sekund!</red>'
placeholders:
infinite: Nekonečný
my-island-phase-default: Neznámá
gui:
titles:
phases: '&0&l Jednoblokové fáze'
phases: '<black><bold>Jednoblokové fáze</bold></black>'
buttons:
previous:
name: '&f&l Předchozí stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Předchozí stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
next:
name: '&f&l Další stránka'
description: '&7 Přepnout na stránku [number]'
name: '<white><bold>Další stránka</bold></white>'
description: '<gray>Přepnout na stránku [number]</gray>'
phase:
name: '&f&l [phase]'
name: '<white><bold>[phase]</bold></white>'
description: |-
[starting-block]
[biome]
[bank]
[economy]
[level]
[permission]
starting-block: '&7 Spustí se po rozbití bloků &e [number].'
biome: '&7 Biom: &e [biome]'
bank: '&7 Vyžaduje &e $[number] &7 na bankovním účtu.'
economy: '&7 Vyžaduje &e $[number] &7 v hráčském účtu.'
level: '&7 Vyžaduje &e [number] &7 úroveň ostrova.'
permission: '&7 Vyžaduje oprávnění `&e[permission]&7`.'
blocks-prefix: '&7 Bloků ve fázi -'
blocks: '&e [name], '
starting-block: '<gray>Spustí se po rozbití bloků </gray><yellow>[number].</yellow>'
biome: '<gray>Biom: </gray><yellow>[biome]</yellow>'
bank: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>na bankovním účtu.</gray>'
economy: '<gray>Vyžaduje </gray><yellow>$[number] </yellow><gray>v hráčském účtu.</gray>'
level: '<gray>Vyžaduje </gray><yellow>[number] </yellow><gray>úroveň ostrova.</gray>'
permission: '<gray>Vyžaduje oprávnění `</gray><yellow>[permission]</yellow><gray>`.</gray>'
blocks-prefix: '<gray>Bloků ve fázi -</gray>'
blocks: '<yellow>[name], </yellow>'
wrap-at: '50'
tips:
click-to-previous: '&e Klepnutím na &7 zobrazíte předchozí stránku.'
click-to-next: '&e Klepnutím na &7 zobrazíte další stránku.'
click-to-change: '&e Klikněte na &7 pro změnu.'
click-to-previous: '<yellow>Klepnutím na </yellow><gray>zobrazíte předchozí stránku.</gray>'
click-to-next: '<yellow>Klepnutím na </yellow><gray>zobrazíte další stránku.</gray>'
click-to-change: '<yellow>Klikněte na </yellow><gray>pro změnu.</gray>'
island:
starting-hologram: |-
&a Vítejte v AOneBlock
&e Prolomte tento blok
<green>Vítejte v AOneBlock
</green><yellow>Prolomte tento blok</yellow>
Loading
Loading