From 0e91775a4972f6fa5bdc043fdbae614271b5cd42 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 22 Oct 2023 03:44:53 +0000 Subject: [PATCH 01/42] Translate cs.yml via GitLocalize --- src/main/resources/locales/cs.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index 89bb0291..3541d7de 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -3,9 +3,10 @@ aoneblock: commands: admin: setcount: - parameters: " " + parameters: " [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]" setchest: parameters: " " description: dejte pohled na hrudník do fáze se specifikovanou vzácností @@ -24,6 +25,9 @@ aoneblock: count: description: zobrazit počet bloků a fázi info: "&a Jste na bloku &b [number] ve fázi &a [name]" + info: + count: Ostrov &a je na bloku &b [number]&a ve fázi &b [name] &a. Počet doživotí + &b [lifetime] &a. phases: description: zobrazit seznam všech fází title: "&2 Fáze OneBlock" @@ -34,14 +38,31 @@ aoneblock: parameters: "" set: "&a Počet nastaven na [number]." too-high: "&c Maximálně můžeš nastavit [number]!" + respawn-block: {} 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: Nemůžeš pokračovat, dokud nedostaneš oprávnění [name]. placeholders: infinite: Nekonečný + gui: + titles: {} + buttons: + previous: + description: "&7 Přepnout na stránku [number]" + next: + description: "&7 Přepnout na stránku [number]" + phase: + name: "&f&l [phase]" + description: "[starting-block]\n[biome]\n[bank]\n[economy] \n[level]\n[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`." + tips: {} island: starting-hologram: |- &a Vítejte v AOneBlock From d08b2ae529cda9de5285cf091f0cbd8b710ac7ce Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Sun, 22 Oct 2023 03:44:53 +0000 Subject: [PATCH 02/42] Translate cs.yml via GitLocalize --- src/main/resources/locales/cs.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index 3541d7de..8edd1981 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -36,22 +36,30 @@ aoneblock: island: setcount: parameters: "" + 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]!" - respawn-block: {} + 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." 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í [jméno]!" placeholders: infinite: Nekonečný gui: - titles: {} + titles: + phases: "&0&l Jednoblokové fáze" buttons: previous: + name: "&f&l Předchozí stránka" description: "&7 Přepnout na stránku [number]" next: + name: "&f&l Další stránka" description: "&7 Přepnout na stránku [number]" phase: name: "&f&l [phase]" @@ -62,7 +70,10 @@ aoneblock: 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`." - tips: {} + 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." island: starting-hologram: |- &a Vítejte v AOneBlock From c0d04b0185d8ce12452d02f8d026c6446860777c Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 22 Oct 2023 03:47:23 +0000 Subject: [PATCH 03/42] Translate de.yml via GitLocalize --- src/main/resources/locales/de.yml | 36 ++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 35902341..4b67d218 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -3,9 +3,10 @@ aoneblock: commands: admin: setcount: - parameters: " " + parameters: " [lifetime]" description: Setze die Blockanzahl des Spielers set: "&a [name] zählt auf [number]" + set-lifetime: Die Lebenszeitanzahl von &a [name] wurde auf [number] gesetzt setchest: parameters: " " description: Versetzen Sie die betrachtete Truhe in eine Phase mit der angegebenen @@ -28,11 +29,18 @@ aoneblock: count: description: Zeige die Blockanzahl und Phase info: "&a Sie befinden sich in der Phase &a [name] in Block &b [number]" + info: + count: "&a Island befindet sich in Block &b [number]&a in der Phase &b [Name] + &a. Lebenszeitanzahl &b [lifetime] &a." phases: description: Zeigen Sie eine Liste aller Phasen an title: "&2 OneBlock-Phasen" name-syntax: "&a [name]" description-syntax: "&b [number] Blöcke" + island: + setcount: + too-high: "&c Das Maximum, das Sie festlegen können, ist [number]!" + respawn-block: {} phase: insufficient-level: "&c Ihr Insellevel ist zu niedrig, um fortzufahren! Es muss [number] sein." @@ -40,10 +48,32 @@ aoneblock: [number] sein." insufficient-bank-balance: "&c Der Saldo der Inselbank ist zu niedrig, um fortzufahren! Es muss [number] sein." - insufficient-permission: "&c Sie können nicht weitermachen, bis Sie die Berechtigung - [name] erhalten haben!" + insufficient-permission: "&c Sie können nicht weitermachen, bis Sie die [name]-Berechtigung + erhalten!" placeholders: infinite: Unendlich + gui: + titles: {} + buttons: + previous: + description: "&7 Zur Seite [number] wechseln" + next: + description: "&7 Zur Seite [number] wechseln" + phase: + name: "&f&l [phase]" + description: |- + [starting-block] + [biome] + [bank] + [economy] + [level] + [permission] + starting-block: "&7 Startet nach dem Aufbrechen von &e [number] Blöcken." + biome: "&7 Biom: &e [biome]" + bank: "&7 Erfordert &e $[number] &7 auf dem Bankkonto." + economy: "&7 Erfordert &e $[number] &7 im Spielerkonto." + level: "&7 Erfordert &e [number] &7 Inselebene." + tips: {} island: starting-hologram: |- &aWillkommen bei AOneBlock From 2158cb1942452b57f7c0bc5aa459530480d5c705 Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Sun, 22 Oct 2023 03:47:26 +0000 Subject: [PATCH 04/42] Translate de.yml via GitLocalize --- src/main/resources/locales/de.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index 4b67d218..d2671752 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -39,8 +39,15 @@ aoneblock: description-syntax: "&b [number] Blöcke" island: setcount: + parameters: "" + description: Setzen Sie die Blockanzahl auf den zuvor abgeschlossenen Wert too-high: "&c Das Maximum, das Sie festlegen können, ist [number]!" - respawn-block: {} + respawn-block: + description: lässt den magischen Block in Situationen wieder erscheinen, in + denen er verschwindet + block-exist: "&ein Block existiert, musste nicht neu gestartet werden. Ich habe + es für dich markiert." + block-respawned: "&a Block wieder aufgetaucht." phase: insufficient-level: "&c Ihr Insellevel ist zu niedrig, um fortzufahren! Es muss [number] sein." @@ -73,7 +80,10 @@ aoneblock: bank: "&7 Erfordert &e $[number] &7 auf dem Bankkonto." economy: "&7 Erfordert &e $[number] &7 im Spielerkonto." level: "&7 Erfordert &e [number] &7 Inselebene." - tips: {} + permission: "&7 Erfordert die Berechtigung „&e[permission]&7“." + tips: + click-to-previous: "&e Klicken Sie auf &7, um die vorherige Seite anzuzeigen." + click-to-next: "&e Klicken Sie auf &7, um die nächste Seite anzuzeigen." island: starting-hologram: |- &aWillkommen bei AOneBlock From 92ca647d062f5e21a458999fb7003c8dcf104104 Mon Sep 17 00:00:00 2001 From: EinsLucaaa Date: Sun, 22 Oct 2023 03:47:29 +0000 Subject: [PATCH 05/42] Translate de.yml via GitLocalize --- src/main/resources/locales/de.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index d2671752..f58c4232 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -41,6 +41,7 @@ aoneblock: setcount: parameters: "" description: Setzen Sie die Blockanzahl auf den zuvor abgeschlossenen Wert + set: "&a Zähler auf [number] gesetzt." too-high: "&c Das Maximum, das Sie festlegen können, ist [number]!" respawn-block: description: lässt den magischen Block in Situationen wieder erscheinen, in @@ -60,11 +61,14 @@ aoneblock: placeholders: infinite: Unendlich gui: - titles: {} + titles: + phases: "&0&l OneBlock-Phasen" buttons: previous: + name: "&f&l Vorherige Seite" description: "&7 Zur Seite [number] wechseln" next: + name: "&f&l Nächste Seite" description: "&7 Zur Seite [number] wechseln" phase: name: "&f&l [phase]" @@ -84,6 +88,7 @@ aoneblock: tips: click-to-previous: "&e Klicken Sie auf &7, um die vorherige Seite anzuzeigen." click-to-next: "&e Klicken Sie auf &7, um die nächste Seite anzuzeigen." + click-to-change: "&e Zum Ändern &7klicken." island: starting-hologram: |- &aWillkommen bei AOneBlock From c82aadb25569558cfcf2af752d1157d32f6f5586 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 22 Oct 2023 03:51:21 +0000 Subject: [PATCH 06/42] Translate ja.yml via GitLocalize --- src/main/resources/locales/ja.yml | 61 ++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index 7e6dc052..b9966d4a 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -2,29 +2,64 @@ aoneblock: commands: admin: - sanity: - description: コンソールに位相確率の健全性チェックを表示する - parameters: "<フェーズ>" - see-console: "&aコンソールでレポートを表示" + setcount: + parameters: "<名前> <数>" + description: プレイヤーのブロック数を設定する + set: "[name]の数が[number]に設定されました" + set-lifetime: "&a [name] の有効期間カウントが [number] に設定されました" setchest: - chest-is-empty: "&cそのチェストは空なので追加できません" + parameters: "<フェーズ> <レア度>" description: 見つめられた胸部を、指定された希少性を持つフェーズに置く - failure: "&cチェストをフェーズに追加できませんでした!エラーについてはコンソールを参照してください" + chest-is-empty: "&cそのチェストは空なので追加できません" + unknown-phase: "&c不明なフェーズ。タブコンプリートを使用して表示します" + unknown-rarity: "&c希少性は不明です。 COMMON、UNCOMMON、RARE、またはEPICを使用します" look-at-chest: "&c満たされた箱を見てそれを設定します" only-single-chest: "&c単一のチェストのみを設定できます" - parameters: "<フェーズ> <レア度>" success: "&a胸部がフェーズに追加されました" - unknown-phase: "&c不明なフェーズ。タブコンプリートを使用して表示します" - unknown-rarity: "&c希少性は不明です。 COMMON、UNCOMMON、RARE、またはEPICを使用します" - setcount: - description: プレイヤーのブロック数を設定する - parameters: "<名前> <数>" - set: "[name]の数が[number]に設定されました" + failure: "&cチェストをフェーズに追加できませんでした!エラーについてはコンソールを参照してください" + sanity: + parameters: "<フェーズ>" + description: コンソールに位相確率の健全性チェックを表示する + see-console: "&aコンソールでレポートを表示" count: description: ブロック数とフェーズを表示する info: "[name]フェーズのブロック[number]にいます" + info: + count: "&a 島は &b [name] &a フェーズのブロック &b [number]&a 上にあります。生涯カウント &b [lifetime] + &a。" phases: description: すべてのフェーズのリストを表示する title: "&2 OneBlockフェーズ" name-syntax: "&a[name]" description-syntax: "&b [number]ブロック" + island: + setcount: + too-high: "&c 設定できる最大値は [number] です!" + respawn-block: {} + phase: + insufficient-level: "&c 島のレベルが低すぎるので先に進めません! [number] である必要があります。" + insufficient-funds: "&c 資金が少なすぎるため続行できません。 [number] である必要があります。" + insufficient-bank-balance: "&c 島の銀行残高が少なすぎるため続行できません。 [number] である必要があります。" + insufficient-permission: "&c [name] の許可を取得するまで、これ以上先に進むことはできません。" + placeholders: {} + gui: + titles: {} + buttons: + previous: + description: "&7 [number]ページに切り替えます" + next: {} + phase: + name: "&f&l [phase]" + description: |- + [starting-block] + [biome] + [bank] + [economy] + [level] + [permission] + starting-block: "&7 &e [number] ブロックを分割した後に開始します。" + biome: "&7 バイオーム: &e [biome]" + bank: "&7 銀行口座に &e $[number] &7 が必要です。" + level: "&7 &e [number] &7 の島レベルが必要です。" + tips: {} + island: {} From db267d7d2c925e1887d7b284eb483cb0d7869cc8 Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Sun, 22 Oct 2023 03:51:22 +0000 Subject: [PATCH 07/42] Translate ja.yml via GitLocalize --- src/main/resources/locales/ja.yml | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index b9966d4a..0de9eb37 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -34,20 +34,31 @@ aoneblock: description-syntax: "&b [number]ブロック" island: setcount: + parameters: "<カウント>" + description: ブロック数を以前に完了した値に設定する + set: "&a カウントを [数値] に設定します。" too-high: "&c 設定できる最大値は [number] です!" - respawn-block: {} + respawn-block: + description: マジックブロックが消えた場合に再出現します + block-exist: "&a ブロックが存在します。再生成は必要ありませんでした。私はあなたのためにそれをマークしました。" + block-respawned: "&a ブロックが復活しました。" phase: insufficient-level: "&c 島のレベルが低すぎるので先に進めません! [number] である必要があります。" insufficient-funds: "&c 資金が少なすぎるため続行できません。 [number] である必要があります。" insufficient-bank-balance: "&c 島の銀行残高が少なすぎるため続行できません。 [number] である必要があります。" insufficient-permission: "&c [name] の許可を取得するまで、これ以上先に進むことはできません。" - placeholders: {} + placeholders: + infinite: 無限 gui: - titles: {} + titles: + phases: "&0&l ワンブロックフェーズ" buttons: previous: + name: "&f&l 前のページ" description: "&7 [number]ページに切り替えます" - next: {} + next: + name: "&f&l 次のページ" + description: "&7 [番号]ページに切り替えます" phase: name: "&f&l [phase]" description: |- @@ -60,6 +71,14 @@ aoneblock: starting-block: "&7 &e [number] ブロックを分割した後に開始します。" biome: "&7 バイオーム: &e [biome]" bank: "&7 銀行口座に &e $[number] &7 が必要です。" + economy: "&7 プレイヤーアカウントに &e $[number] &7 が必要です。" level: "&7 &e [number] &7 の島レベルが必要です。" - tips: {} - island: {} + permission: "&7 `&e[permission]&7` 権限が必要です。" + tips: + click-to-previous: "&e &7 をクリックして前のページを表示します。" + click-to-next: "&e &7 をクリックして次のページを表示します。" + click-to-change: "&e &7 をクリックして変更します。" + island: + starting-hologram: |- + &aAOneBlock へようこそ + &eこのブロックを壊して開始してください From 957587459264a9c043c43ed03c3c93294f22122c Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Sun, 22 Oct 2023 03:53:47 +0000 Subject: [PATCH 08/42] Translate zh-CN.yml via GitLocalize --- src/main/resources/locales/zh-CN.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index f140358e..c17effc6 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -24,6 +24,7 @@ aoneblock: count: description: 显示方块数量和阶段 info: "&a 您当前挖掘的方块数量是 &b [number], 为 &a [name] 阶段" + info: {} phases: description: 显示所有阶段的列表 title: "&2 OneBlock 阶段" @@ -35,13 +36,33 @@ aoneblock: description: 将块计数设置为先前完成的值 set: "&a 数量设置为 [number]." too-high: "&c 你最大只能设置 [number]!" + respawn-block: + description: 在魔法块消失的情况下重生 + block-exist: "&a 块存在,不需要重生。我给你标记了。" + block-respawned: "&a 块重生。" phase: insufficient-level: "&c 岛屿等级过低, 无法执行此操作! 等级必须达到 [number]." insufficient-funds: "&c 余额不足, 无法执行此操作! 余额应多于 [number]." insufficient-bank-balance: "&c 岛屿银行余额不足, 无法执行此操作! 余额应多于 [number]." - insufficient-permission: "&c 您不能继续执行操作了, 因为没有 [name] 权限!" + insufficient-permission: "&c 在获得 [name] 许可之前,您不能继续操作!" placeholders: infinite: 无限 + gui: + titles: + phases: "&0&l OneBlock 阶段" + buttons: + previous: + name: "&f&l 上一页" + next: + name: "&f&l 下一页" + phase: + starting-block: "&7 在破坏 &e [number] 块后开始。" + bank: "&7 需要银行帐户中有 &e $[number] &7。" + economy: "&7 需要玩家帐户中有 &e $[number] &7。" + tips: + click-to-previous: "&e 单击&7 查看上一页。" + click-to-next: "&e 单击 &7 查看下一页。" + click-to-change: "&e 单击 &7 进行更改。" island: starting-hologram: |- &a欢迎来到 AOneBlock From f2c4db134f33e0015a7ca0148fa1b9d9d8e43526 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 22 Oct 2023 03:53:48 +0000 Subject: [PATCH 09/42] Translate zh-CN.yml via GitLocalize --- src/main/resources/locales/zh-CN.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index c17effc6..4b16fbb9 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -24,7 +24,8 @@ aoneblock: count: description: 显示方块数量和阶段 info: "&a 您当前挖掘的方块数量是 &b [number], 为 &a [name] 阶段" - info: {} + info: + count: "&a 岛位于 &b [name] &a 阶段的 &b [number]&a 区块。生命周期计数 &b [lifetime] &a。" phases: description: 显示所有阶段的列表 title: "&2 OneBlock 阶段" @@ -53,12 +54,25 @@ aoneblock: buttons: previous: name: "&f&l 上一页" + description: "&7 切换到[number]页" next: name: "&f&l 下一页" + description: "&7 切换到[number]页" phase: + name: "&f&l [phase]" + description: |- + [starting-block] + [biome] + [bank] + [economy] + [level] + [permission] starting-block: "&7 在破坏 &e [number] 块后开始。" + biome: "&7 生物群落:&e [biome]" bank: "&7 需要银行帐户中有 &e $[number] &7。" economy: "&7 需要玩家帐户中有 &e $[number] &7。" + level: "&7 需要 &e [number] &7 岛屿等级。" + permission: "&7 需要 `&e[permission]&7` 权限。" tips: click-to-previous: "&e 单击&7 查看上一页。" click-to-next: "&e 单击 &7 查看下一页。" From 19ff4907693878e1eefae704770d6fa88fe5e93a Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 22 Oct 2023 03:57:13 +0000 Subject: [PATCH 10/42] Translate zh-TW.yml via GitLocalize --- src/main/resources/locales/zh-TW.yml | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main/resources/locales/zh-TW.yml b/src/main/resources/locales/zh-TW.yml index e7147e6d..032521a2 100644 --- a/src/main/resources/locales/zh-TW.yml +++ b/src/main/resources/locales/zh-TW.yml @@ -23,8 +23,37 @@ aoneblock: count: description: 顯示塊數和相位 info: "&a您正在&a[name]]階段中阻止&b[number]" + info: + count: "&a 島位於 &b [names] &a 階段的 &b [number]&a 區塊。生命週期計數 &b [lifetime] &a。" phases: description: 顯示所有階段的列表 title: "&2 OneBlock階段" name-syntax: "&a [name]" description-syntax: "&b [number]塊" + island: + setcount: {} + respawn-block: {} + phase: + insufficient-level: "&c 你的島嶼等級太低,無法繼續!必須是[number]。" + insufficient-bank-balance: "&c 島上銀行餘額太低,無法繼續!必須是[number]。" + placeholders: {} + gui: + titles: {} + buttons: + previous: + description: "&7 切換到[number]頁" + next: + description: "&7 切換到[number]頁" + phase: + description: |- + [starting-block] + [biome] + [bank] + [economy] + [level] + [permission] + biome: "&7 生物群落:&e [biome]" + level: "&7 需要 &e [number] &7 島嶼等級。" + permission: "&7 需要 `&e[permission]&7` 權限。" + tips: {} + island: {} From 581d1fe705ab8a732d4ee9d2691d8721135ef832 Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Sun, 22 Oct 2023 03:57:13 +0000 Subject: [PATCH 11/42] Translate zh-TW.yml via GitLocalize --- src/main/resources/locales/zh-TW.yml | 36 +++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/main/resources/locales/zh-TW.yml b/src/main/resources/locales/zh-TW.yml index 032521a2..8828d03b 100644 --- a/src/main/resources/locales/zh-TW.yml +++ b/src/main/resources/locales/zh-TW.yml @@ -6,6 +6,7 @@ aoneblock: parameters: "<名稱> <計數>" description: 設置玩家的蓋帽數 set: a [name]的計數設置為[number] + set-lifetime: "&a [name] 的生命週期計數設定為 [number]" setchest: parameters: "<階段> <稀有>" description: 將所看的箱子放在指定稀有度的階段 @@ -31,20 +32,34 @@ aoneblock: name-syntax: "&a [name]" description-syntax: "&b [number]塊" island: - setcount: {} - respawn-block: {} + setcount: + parameters: "<計數>" + description: 將區塊計數設定為之前完成的值 + set: "&a 計數設定為 [number]。" + too-high: "&c 您可以設定的最大值是[number]!" + respawn-block: + description: 在魔法塊消失的情況下重生 + block-exist: "&a 塊存在,不需要重生。我給你標記了。" + block-respawned: "&a 塊重生。" phase: insufficient-level: "&c 你的島嶼等級太低,無法繼續!必須是[number]。" + insufficient-funds: "&c 您的資金太低,無法繼續!他們必須是[數字]。" insufficient-bank-balance: "&c 島上銀行餘額太低,無法繼續!必須是[number]。" - placeholders: {} + insufficient-permission: "&c 在獲得 [name] 許可之前,您不能繼續操作!" + placeholders: + infinite: 無窮 gui: - titles: {} + titles: + phases: "&0&l OneBlock 階段" buttons: previous: + name: "&f&l 上一頁" description: "&7 切換到[number]頁" next: + name: "&f&l 下一頁" description: "&7 切換到[number]頁" phase: + name: "&f&l [階段]" description: |- [starting-block] [biome] @@ -52,8 +67,17 @@ aoneblock: [economy] [level] [permission] + starting-block: "&7 在破壞 &e [number] 區塊後開始。" biome: "&7 生物群落:&e [biome]" + bank: "&7 需要銀行帳戶中有 &e $[number] &7。" + economy: "&7 需要玩家帳號中有 &e $[number] &7。" level: "&7 需要 &e [number] &7 島嶼等級。" permission: "&7 需要 `&e[permission]&7` 權限。" - tips: {} - island: {} + tips: + click-to-previous: "&e 點選&7 查看上一頁。" + click-to-next: "&e 點選&7 查看下一頁。" + click-to-change: "&e 點選 &7 進行更改。" + island: + starting-hologram: |- + &a歡迎來到 AOneBlock + 打破此區塊以開始(&E) From 77cac152c3c6e6a685af2d98f43ca363e137f47f Mon Sep 17 00:00:00 2001 From: LordNeoZ Date: Sun, 22 Oct 2023 16:19:28 +0000 Subject: [PATCH 12/42] Translate es.yml via GitLocalize --- src/main/resources/locales/es.yml | 65 ++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index d2ddcf7e..e40aabb9 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -3,27 +3,31 @@ aoneblock: commands: admin: setcount: - parameters: " [lifetime]" - description: "Establece un número de bloques minados al jugador" + parameters: " [lifetime]" + description: Establece el número de bloques minados al jugador set: "&aEl número de bloques minados de [name] se ha establecido en [number]" - set-lifetime: "&aEl numero de bloques en forma permanente de [name] se ha establecido en [number]" + set-lifetime: "&aEl numero de bloques totales de [name] se ha establecido + en [number]" setchest: - parameters: " " - description: "Coloca un cofre al que apuntes en una fase con la rareza especificada" + parameters: " " + description: Coloca el cofre que estas mirando en una fase con la rareza especificada chest-is-empty: "&cEse cofre está vacío, así que no se puede agregar" unknown-phase: "&cFase desconocida. Presione TAB para verlas" unknown-rarity: "&cRareza desconocida. Use COMMON, UNCOMMON, RARE o EPIC" look-at-chest: "&cApunta hacia un cofre lleno para configurarlo" only-single-chest: "&cSolo se pueden configurar cofres individuales" success: "&aEl cofre ha sido agregado con éxito a la fase" - failure: "&c¡No se pudo agregar el cofre a la fase! Revisa la consola para más detalles" + failure: "&c¡No se pudo agregar el cofre a la fase! Revisa la consola para + más detalles" sanity: - parameters: "" - description: "Muestra la proporción detallada de las probabilidades durante las fases en la consola" + parameters: "" + description: Muestra una comprobación de las probabilidades de la fase en + la consola see-console: "&aRevisa la consola para ver el informe" count: description: Muestra el número de bloques minados y la fase correspondiente info: "&aTienes &b[number] bloques minados en la fase &a[name]" + info: {} phases: description: Muestra una lista de todas las fases title: "&2Fases de OneBlock" @@ -31,16 +35,49 @@ aoneblock: description-syntax: "&b[number] bloques" island: setcount: - parameters: "" - description: "Establece la cantidad de bloques a un valor previamente completado" + parameters: "" + description: Establece la cantidad de bloques a un valor previamente completado set: "&aCantidad establecida en [number]." too-high: "&c¡Lo máximo que puedes establecer es [number]!" + respawn-block: {} phase: - insufficient-level: "&c¡Tu nivel de isla es demasiado bajo para seguir! Este debe ser de [number]." + insufficient-level: "&c¡Tu nivel de isla es demasiado bajo para seguir! Este debe + ser de [number]." insufficient-funds: "&c¡Tus fondos son insuficientes! Debes tener [number]." - insufficient-bank-balance: "&c¡El dinero del banco en la isla es demasiado bajo para seguir! Debes tener [number]." - insufficient-permission: "&cNo puedes seguir hasta conseguir el permiso [name]" + insufficient-bank-balance: "&c¡El dinero del banco en la isla es demasiado bajo + para seguir! Debes tener [number]." placeholders: infinite: Infinito + gui: + titles: + phases: "&0&l Fases de OneBlock" + buttons: + previous: + name: "&f&l Pagina Anterior" + description: "&7 Ir a la pagina [number]" + next: + name: "&f&l Siguiente pagina" + description: "&7 Ir a la pagina [number]" + phase: + name: "&f&l [phase]" + description: |- + [starting-block] + [biome] + [bank] + [economy] + [level] + [permission] + starting-block: "&7 Comienza tras romper &e [number] bloques." + biome: "&7 Bioma: &e [biome]" + bank: "&7 Requiere &e $[number] &7 en la cuenta del banco." + economy: "&7 Requiere &e $[number] &7 en la cuenta del jugador." + level: "&7 Requiere &e [number] &7 nivel de isla." + permission: "&7 Requiere permiso `&e[permission]&7`." + tips: + click-to-previous: "&e Click &7 para ver pagina anterior." + click-to-next: "&e Click &7 para ver pagina siguiente." + click-to-change: "&e Click &7 para cambiar." island: - starting-hologram: "&aBienvenido a AOneBlock\n&eRompe este bloque para empezar" + starting-hologram: |- + &aBienvenido a AOneBlock + &eRompe este bloque para empezar From f0d5d36555bc1d85ac3341b66e4dec222df0e20a Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 22 Oct 2023 16:19:31 +0000 Subject: [PATCH 13/42] Translate es.yml via GitLocalize --- src/main/resources/locales/es.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index e40aabb9..40ae8ea4 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -27,7 +27,9 @@ aoneblock: count: description: Muestra el número de bloques minados y la fase correspondiente info: "&aTienes &b[number] bloques minados en la fase &a[name]" - info: {} + info: + count: "&a Island está en el bloque &b [number]&a en la fase &b [name] &a. Recuento + de vida &b [lifetime] &a." phases: description: Muestra una lista de todas las fases title: "&2Fases de OneBlock" @@ -46,6 +48,8 @@ aoneblock: insufficient-funds: "&c¡Tus fondos son insuficientes! Debes tener [number]." insufficient-bank-balance: "&c¡El dinero del banco en la isla es demasiado bajo para seguir! Debes tener [number]." + insufficient-permission: "&c ¡No puede continuar hasta que obtenga el permiso + de [name]!" placeholders: infinite: Infinito gui: From 0a67ffb728cf71ed9bde381d47454e51f5030f9f Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Sun, 22 Oct 2023 16:19:32 +0000 Subject: [PATCH 14/42] Translate es.yml via GitLocalize --- src/main/resources/locales/es.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index 40ae8ea4..5a06d1dd 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -41,7 +41,10 @@ aoneblock: description: Establece la cantidad de bloques a un valor previamente completado set: "&aCantidad establecida en [number]." too-high: "&c¡Lo máximo que puedes establecer es [number]!" - respawn-block: {} + respawn-block: + description: reaparece el bloque mágico en situaciones en las que desaparece + block-exist: "&a Block existe, no requirió reaparición. Te lo marqué." + block-respawned: "& un bloque reapareció." phase: insufficient-level: "&c¡Tu nivel de isla es demasiado bajo para seguir! Este debe ser de [number]." From f3bde65b9cde3bc94343a077f51324474172bde3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 22 Oct 2023 16:23:36 +0000 Subject: [PATCH 15/42] Translate fr.yml via GitLocalize --- src/main/resources/locales/fr.yml | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index 792580db..eb1a6210 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -5,7 +5,7 @@ aoneblock: setcount: parameters: " [DuréeDeVie]" description: Définir le nombre de blocks du joueur - set: "&a Le compte de [nom] est défini sur [nombre]." + set: "&a Le compte de [name] est défini sur [number]." set-lifetime: "&a La durée de vie de [name] est de [number]" setchest: parameters: " " @@ -26,6 +26,9 @@ aoneblock: count: description: afficher le nombre de blocs et la phase info: "&a Vous êtes sur le bloc &b [number] dans la phase &a [name]" + info: + count: "&a L'île est sur le bloc &b [number]&a dans la phase &b [name] &a. Nombre + de durée de vie &b [lifetime] &a." phases: description: afficher une liste de toutes les phases title: "&2 Phases OneBlock" @@ -37,11 +40,7 @@ aoneblock: description: définir le nombre de blocs à la valeur précédemment terminée set: "&a Nombre défini sur [number]." too-high: "&c Le maximum que vous pouvez définir est [number] !" - respawn-block: - description: Force la réapparition du bloc magique dans le cas ou il disparait - block-exist: "&a Le bloc magique existe déja, inutile de le faire réapparaitre. - Je l'ai marqué pour toi." - block-respawned: "&a Bloc magique réapparu, s'il te plait, ne recommence pas." + respawn-block: {} phase: insufficient-level: Ton niveau d'île est trop bas ! Il doit être de [number] au minimum. @@ -49,8 +48,8 @@ aoneblock: [number]. insufficient-bank-balance: Ta banque d'île n'a pas les fonds nécessaire ! Vous devez au moins avoir [number]. - insufficient-permission: 'Tu ne peux plus avancer, il te manque la permission - : [name] !' + insufficient-permission: "&c Vous ne pouvez pas continuer jusqu'à ce que vous + obteniez l'autorisation de [name] !" placeholders: infinite: Infini gui: @@ -66,12 +65,12 @@ aoneblock: phase: name: "&f&l [phase]" description: |- - [Bloc de départ] + [starting-block] [biome] - [banque] - [économie] - [niveau] - [autorisation] + [bank] + [economy] + [level] + [permission] starting-block: "&7 Commence après avoir détruit &e [number] blocs." biome: "&7 Biome : &e [biome]" bank: "&7 Requiert &e $[number] &7 dans ta banque." From 6902ded823ddc867c296aeb7661349d5e3349b8c Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Sun, 22 Oct 2023 16:23:37 +0000 Subject: [PATCH 16/42] Translate fr.yml via GitLocalize --- src/main/resources/locales/fr.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index eb1a6210..b7b6235f 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -40,7 +40,11 @@ aoneblock: description: définir le nombre de blocs à la valeur précédemment terminée set: "&a Nombre défini sur [number]." too-high: "&c Le maximum que vous pouvez définir est [number] !" - respawn-block: {} + respawn-block: + description: réapparaît le bloc magique dans les situations où il disparaît + block-exist: "&un bloc existe, n'a pas nécessité de réapparition. Je l'ai noté + pour toi." + block-respawned: "&un bloc réapparu." phase: insufficient-level: Ton niveau d'île est trop bas ! Il doit être de [number] au minimum. From 0c0d5d9a30391626b0db68c969ce476b2bbad0ad Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 22 Oct 2023 16:27:53 +0000 Subject: [PATCH 17/42] Translate hr.yml via GitLocalize --- src/main/resources/locales/hr.yml | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml index 0704926e..0c22f1a4 100644 --- a/src/main/resources/locales/hr.yml +++ b/src/main/resources/locales/hr.yml @@ -6,6 +6,7 @@ aoneblock: parameters: " " description: postavljanje broja blokova igrača set: "&a broj [name] postavljen je na [number]" + set-lifetime: "&broj životnog vijeka [name] postavljen na [number]" setchest: parameters: " " description: stavite pregledani sanduk u fazu s specificiranom rijetkošću @@ -24,8 +25,46 @@ aoneblock: count: description: prikazuju broj i fazu bloka info: "&a Nalazite se na bloku &b [number] u fazi &a [name]" + info: + count: "&a Otok je u bloku &b [number]&a u &b [name] &a fazi. Životni vijek + &b [lifetime] &a." phases: description: prikažite popis svih faza title: "&2 OneBlock Faze" name-syntax: "&a [name]" description-syntax: "&b [number] blokova" + island: + setcount: + set: "&a Brojanje postavljeno na [number]." + too-high: "&c Maksimalno što možete postaviti je [number]!" + respawn-block: {} + phase: + insufficient-level: "&c Vaša razina otoka je preniska za nastavak! Mora biti [number]." + insufficient-funds: "&c Vaša su sredstva premala za nastavak! Moraju biti [number]." + insufficient-bank-balance: "&c Stanje otočne banke je premalo za nastavak! Mora + biti [number]." + insufficient-permission: "&c Ne možete nastaviti dok ne dobijete dopuštenje [name]!" + placeholders: {} + gui: + titles: {} + buttons: + previous: + description: "&7 Prijeđi na stranicu [number]." + next: + description: "&7 Prijeđi na stranicu [number]." + phase: + name: "&f&l [phase]" + description: |- + [starting-block] + [biome] + [bank] + [economy] + [level] + [permission] + starting-block: "&7 Počinje nakon razbijanja &e [number] blokova." + biome: "&7 Biome: &e [biome]" + bank: "&7 Zahtijeva &e $[number] &7 na bankovnom računu." + economy: "&7 Zahtijeva &e $[number] &7 na računu igrača." + level: "&7 Zahtijeva &e [number] &7 razinu otoka." + tips: {} + island: {} From a23a9cb23d23941fb9d4cf6bf2571463e44cab23 Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Sun, 22 Oct 2023 16:27:54 +0000 Subject: [PATCH 18/42] Translate hr.yml via GitLocalize --- src/main/resources/locales/hr.yml | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml index 0c22f1a4..22cf5ad4 100644 --- a/src/main/resources/locales/hr.yml +++ b/src/main/resources/locales/hr.yml @@ -35,22 +35,32 @@ aoneblock: description-syntax: "&b [number] blokova" island: setcount: + parameters: "" + description: postaviti broj blokova na prethodno dovršenu vrijednost set: "&a Brojanje postavljeno na [number]." too-high: "&c Maksimalno što možete postaviti je [number]!" - respawn-block: {} + respawn-block: + description: ponovno rađa magični blok u situacijama kada nestane + block-exist: "&a blok postoji, nije zahtijevao ponovno stvaranje. Označila sam + za tebe." + block-respawned: "&a blok se ponovno pojavio." phase: insufficient-level: "&c Vaša razina otoka je preniska za nastavak! Mora biti [number]." insufficient-funds: "&c Vaša su sredstva premala za nastavak! Moraju biti [number]." insufficient-bank-balance: "&c Stanje otočne banke je premalo za nastavak! Mora biti [number]." insufficient-permission: "&c Ne možete nastaviti dok ne dobijete dopuštenje [name]!" - placeholders: {} + placeholders: + infinite: Beskonačno gui: - titles: {} + titles: + phases: "&0&l OneBlock faze" buttons: previous: + name: "&f&l Prethodna stranica" description: "&7 Prijeđi na stranicu [number]." next: + name: "&f&l Sljedeća stranica" description: "&7 Prijeđi na stranicu [number]." phase: name: "&f&l [phase]" @@ -66,5 +76,12 @@ aoneblock: bank: "&7 Zahtijeva &e $[number] &7 na bankovnom računu." economy: "&7 Zahtijeva &e $[number] &7 na računu igrača." level: "&7 Zahtijeva &e [number] &7 razinu otoka." - tips: {} - island: {} + permission: "&7 Zahtijeva dozvolu `&e[permission]&7`." + tips: + click-to-previous: "&e Kliknite &7 za pregled prethodne stranice." + click-to-next: "&e Kliknite &7 za pregled sljedeće stranice." + click-to-change: "&e Kliknite &7 za promjenu." + island: + starting-hologram: |- + &aDobro došli u AOneBlock + &eRazbijte ovaj blok za početak From 99eaace082ee5ecb90b91ba550ab66cfe90894b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1roly=20Ozsv=C3=A1rt?= Date: Sun, 22 Oct 2023 16:31:54 +0000 Subject: [PATCH 19/42] Translate hu.yml via GitLocalize --- src/main/resources/locales/hu.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index 5ea83162..b3e8c05a 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -5,11 +5,10 @@ aoneblock: setcount: parameters: " " description: állítsa be a játékos blokkszámát - set: "&a [name] számának beállítása [number]" + set: "&a [name] számának beállítása erre: [number]" setchest: parameters: " " description: helyezze a nézett mellkasát egy szakaszba a megadott ritkasággal - success: és egy mellkas sikeresen hozzáadva a fázishoz chest-is-empty: "&c A mellkas üres, ezért nem adható hozzá" unknown-phase: "&c Ismeretlen fázis. A Tab-Complete használatával megtekintheti őket" @@ -17,6 +16,7 @@ aoneblock: vagy EPIC" look-at-chest: "&c Nézzen meg egy töltött mellkasat, hogy beállítsa" only-single-chest: "&c Csak egyetlen ládát lehet beállítani" + success: és egy mellkas sikeresen hozzáadva a fázishoz failure: "&c A mellkas nem adható hozzá a fázishoz! A hibákat lásd a konzolon" sanity: parameters: "" @@ -26,8 +26,23 @@ aoneblock: count: description: mutassa meg a blokkok számát és a fázist info: "&a Ön a &b [number] blokkban van a &a [name] fázisban" + info: {} phases: description: az összes fázis felsorolása title: "&2 OneBlock Fázis" name-syntax: "&a [name]" description-syntax: "&b [number] blokkolja" + island: + setcount: + parameters: "" + respawn-block: {} + phase: {} + placeholders: {} + gui: + titles: {} + buttons: + previous: {} + next: {} + phase: {} + tips: {} + island: {} From 4e9dc38fc6838110d1479a5fba6f340e497faeeb Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 22 Oct 2023 16:31:57 +0000 Subject: [PATCH 20/42] Translate hu.yml via GitLocalize --- src/main/resources/locales/hu.yml | 37 ++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index b3e8c05a..a4b33402 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -6,6 +6,7 @@ aoneblock: parameters: " " description: állítsa be a játékos blokkszámát set: "&a [name] számának beállítása erre: [number]" + set-lifetime: "&a [name] élettartama a következőre van állítva: [number]" setchest: parameters: " " description: helyezze a nézett mellkasát egy szakaszba a megadott ritkasággal @@ -26,7 +27,9 @@ aoneblock: count: description: mutassa meg a blokkok számát és a fázist info: "&a Ön a &b [number] blokkban van a &a [name] fázisban" - info: {} + info: + count: Az &a sziget a &b [number]&a blokkon található, a &b [name] &a fázisban. + Élettartam száma &b [lifetime] &a. phases: description: az összes fázis felsorolása title: "&2 OneBlock Fázis" @@ -35,14 +38,38 @@ aoneblock: island: setcount: parameters: "" + too-high: "&c A beállítható maximum [number]!" respawn-block: {} - phase: {} + phase: + insufficient-level: "&c A sziget szintje túl alacsony a folytatáshoz! Ennek a + következőnek kell lennie: [number]." + insufficient-funds: "&c A kerete túl kevés a folytatáshoz! Ezeknek [number]-nak + kell lenniük." + insufficient-bank-balance: "&c A sziget banki egyenlege túl alacsony a folytatáshoz! + Ennek a következőnek kell lennie: [number]." + insufficient-permission: "&c Nem folytathatja tovább, amíg meg nem szerzi a [name] + engedélyt!" placeholders: {} gui: titles: {} buttons: - previous: {} - next: {} - phase: {} + previous: + description: "&7 Váltás a [number] oldalra" + next: + description: "&7 Váltás a [number] oldalra" + phase: + name: "&f&l [phase]" + description: |- + [starting-block] + [biome] + [bank] + [economy] + [level] + [permission] + starting-block: "&7 Az &e [number] blokk feltörése után indul." + biome: "&7 életrajz: &e [biome]" + bank: "&7 Szükséges &e $[number] &7 bankszámlára." + economy: "&7 &e $[number] &7 játékos fiókot igényel." + level: "&7 &e [number] &7 szigetszint szükséges." tips: {} island: {} From 63eb91cd0eff64c58ce20a2ae80f66d7bbdd93b4 Mon Sep 17 00:00:00 2001 From: mt-gitlocalize Date: Sun, 22 Oct 2023 16:32:00 +0000 Subject: [PATCH 21/42] Translate hu.yml via GitLocalize --- src/main/resources/locales/hu.yml | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index a4b33402..8ea5a438 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -38,8 +38,14 @@ aoneblock: island: setcount: parameters: "" + description: állítsa be a blokkszámot a korábban kitöltött értékre + set: "&a A számláló értéke [szám]." too-high: "&c A beállítható maximum [number]!" - respawn-block: {} + respawn-block: + description: varázsblokkot hoz újra olyan helyzetekben, amikor eltűnik + block-exist: "&a Blokk létezik, nem igényelt újbóli megjelenést. megjelöltem + neked." + block-respawned: "&a blokk újjáéledt." phase: insufficient-level: "&c A sziget szintje túl alacsony a folytatáshoz! Ennek a következőnek kell lennie: [number]." @@ -49,13 +55,17 @@ aoneblock: Ennek a következőnek kell lennie: [number]." insufficient-permission: "&c Nem folytathatja tovább, amíg meg nem szerzi a [name] engedélyt!" - placeholders: {} + placeholders: + infinite: Végtelen gui: - titles: {} + titles: + phases: "&0&l OneBlock fázisok" buttons: previous: + name: "&f&l Előző oldal" description: "&7 Váltás a [number] oldalra" next: + name: "&f&l Következő oldal" description: "&7 Váltás a [number] oldalra" phase: name: "&f&l [phase]" @@ -71,5 +81,12 @@ aoneblock: bank: "&7 Szükséges &e $[number] &7 bankszámlára." economy: "&7 &e $[number] &7 játékos fiókot igényel." level: "&7 &e [number] &7 szigetszint szükséges." - tips: {} - island: {} + permission: "&7 `&e[permission]&7` engedély szükséges." + tips: + click-to-previous: "&e Kattintson a &7 gombra az előző oldal megtekintéséhez." + click-to-next: "&e Kattintson a &7 gombra a következő oldal megtekintéséhez." + click-to-change: "&e Kattintson a &7 gombra a módosításhoz." + island: + starting-hologram: |- + &aÜdvözlünk az AOneBlockban + &eSzüntesse meg ezt a blokkot a kezdéshez From 60669d85b447e1241fcc887efcf057084f32711e Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 10 Nov 2023 08:00:42 -0800 Subject: [PATCH 22/42] Adds placeholder for #355 --- .../world/bentobox/aoneblock/AOneBlock.java | 569 +++--- .../aoneblock/PlaceholdersManager.java | 450 ++--- .../aoneblock/oneblocks/OneBlocksManager.java | 1578 +++++++++-------- .../oneblocks/OneBlocksManagerTest.java | 296 ---- .../oneblocks/OneBlocksManagerTest2.java | 446 ----- .../oneblocks/OneBlocksManagerTest3.java | 482 +++++ 6 files changed, 1820 insertions(+), 2001 deletions(-) delete mode 100644 src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest.java delete mode 100644 src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest2.java create mode 100644 src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest3.java diff --git a/src/main/java/world/bentobox/aoneblock/AOneBlock.java b/src/main/java/world/bentobox/aoneblock/AOneBlock.java index ea491759..89a1d0b2 100644 --- a/src/main/java/world/bentobox/aoneblock/AOneBlock.java +++ b/src/main/java/world/bentobox/aoneblock/AOneBlock.java @@ -39,281 +39,296 @@ */ public class AOneBlock extends GameModeAddon { - private static final String NETHER = "_nether"; - private static final String THE_END = "_the_end"; - private boolean hasItemsAdder = false; - - // Settings - private Settings settings; - private ChunkGeneratorWorld chunkGenerator; - private final Config configObject = new Config<>(this, Settings.class); - private BlockListener blockListener; - private OneBlocksManager oneBlockManager; - private PlaceholdersManager phManager; - private HoloListener holoListener; - - @Override - public void onLoad() { - // Check if ItemsAdder exists, if yes register listener - if (Bukkit.getPluginManager().getPlugin("ItemsAdder") != null) { - registerListener(new ItemsAdderListener(this)); - hasItemsAdder = true; - } - // Save the default config from config.yml - saveDefaultConfig(); - // Load settings from config.yml. This will check if there are any issues with it too. - if (loadSettings()) { - // Chunk generator - chunkGenerator = settings.isUseOwnGenerator() ? null : new ChunkGeneratorWorld(this); - // Register commands - playerCommand = new PlayerCommand(this); - adminCommand = new AdminCommand(this); - } - } - - private boolean loadSettings() { - // Load settings again to get worlds - settings = configObject.loadConfigObject(); - if (settings == null) { - // Disable - logError("AOneBlock settings could not load! Addon disabled."); - setState(State.DISABLED); - return false; - } else { - // Save the settings - configObject.saveConfigObject(settings); - } - return true; - } - - @Override - public void onEnable() { - oneBlockManager = new OneBlocksManager(this); - if (loadData()) { - // Failed to load - don't register anything - return; - } - blockListener = new BlockListener(this); - registerListener(blockListener); - registerListener(new NoBlockHandler(this)); - registerListener(new BlockProtect(this)); - registerListener(new JoinLeaveListener(this)); - registerListener(new InfoListener(this)); - // Register placeholders - registerPlaceholders(); - - // Register request handlers - registerRequestHandler(new IslandStatsHandler(this)); - registerRequestHandler(new LocationStatsHandler(this)); - - // Register Holograms - holoListener = new HoloListener(this); - registerListener(holoListener); - } - - // Load phase data - public boolean loadData() { - try { - oneBlockManager.loadPhases(); - } catch (IOException e) { - // Disable - logError("AOneBlock settings could not load (oneblock.yml error)! Addon disabled."); - logError(e.getMessage()); - setState(State.DISABLED); - return true; - } - return false; - } - - private void registerPlaceholders() { - phManager = new PlaceholdersManager(this); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_phase", phManager::getPhaseByLocation); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_count", phManager::getCountByLocation); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_phase", phManager::getPhase); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_count", phManager::getCount); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_next_phase", phManager::getNextPhaseByLocation); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_next_phase", phManager::getNextPhase); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_blocks_to_next_phase", phManager::getNextPhaseBlocks); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_blocks_to_next_phase", phManager::getNextPhaseBlocksByLocation); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_percent_done", phManager::getPercentDone); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_percent_done", phManager::getPercentDoneByLocation); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_done_scale", phManager::getDoneScale); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_done_scale", phManager::getDoneScaleByLocation); - // Since 1.10 - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_lifetime_count", phManager::getLifetimeByLocation); - getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_lifetime_count", phManager::getLifetime); - } - - @Override - public void onDisable() { - // save cache - blockListener.saveCache(); - - // Clear holograms - holoListener.clear(); - } - - @Override - public void onReload() { - // save cache - blockListener.saveCache(); - if (loadSettings()) { - log("Reloaded AOneBlock settings"); - } - } - - /** - * @return the settings - */ - public Settings getSettings() { - return settings; - } - - @Override - public void createWorlds() { - String worldName = settings.getWorldName().toLowerCase(); - if (getServer().getWorld(worldName) == null) { - log("Creating AOneBlock world ..."); - } - - // Create the world if it does not exist - islandWorld = getWorld(worldName, World.Environment.NORMAL, chunkGenerator); - // Make the nether if it does not exist - if (settings.isNetherGenerate()) { - if (getServer().getWorld(worldName + NETHER) == null) { - log("Creating AOneBlock's Nether..."); - } - netherWorld = settings.isNetherIslands() ? getWorld(worldName, World.Environment.NETHER, chunkGenerator) : getWorld(worldName, World.Environment.NETHER, null); - } - // Make the end if it does not exist - if (settings.isEndGenerate()) { - if (getServer().getWorld(worldName + THE_END) == null) { - log("Creating AOneBlock's End World..."); - } - endWorld = settings.isEndIslands() ? getWorld(worldName, World.Environment.THE_END, chunkGenerator) : getWorld(worldName, World.Environment.THE_END, null); - } - } - - /** - * Gets a world or generates a new world if it does not exist - * - * @param worldName2 - the overworld name - * @param env - the environment - * @param chunkGenerator2 - the chunk generator. If null then the generator will not be specified - * @return world loaded or generated - */ - private World getWorld(String worldName2, Environment env, ChunkGeneratorWorld chunkGenerator2) { - // Set world name - worldName2 = env.equals(World.Environment.NETHER) ? worldName2 + NETHER : worldName2; - worldName2 = env.equals(World.Environment.THE_END) ? worldName2 + THE_END : worldName2; - WorldCreator wc = WorldCreator.name(worldName2).type(WorldType.FLAT).environment(env); - World w = settings.isUseOwnGenerator() ? wc.createWorld() : wc.generator(chunkGenerator2).createWorld(); - // Set spawn rates - if (w != null) { - setSpawnRates(w); - } - return w; - - } - - private void setSpawnRates(World w) { - if (getSettings().getSpawnLimitMonsters() > 0) { - w.setSpawnLimit(SpawnCategory.MONSTER, getSettings().getSpawnLimitMonsters()); - } - if (getSettings().getSpawnLimitAmbient() > 0) { - w.setSpawnLimit(SpawnCategory.AMBIENT, getSettings().getSpawnLimitAmbient()); - } - if (getSettings().getSpawnLimitAnimals() > 0) { - w.setSpawnLimit(SpawnCategory.ANIMAL, getSettings().getSpawnLimitAnimals()); - } - if (getSettings().getSpawnLimitWaterAnimals() > 0) { - w.setSpawnLimit(SpawnCategory.WATER_ANIMAL, getSettings().getSpawnLimitWaterAnimals()); - } - if (getSettings().getTicksPerAnimalSpawns() > 0) { - w.setTicksPerSpawns(SpawnCategory.ANIMAL, getSettings().getTicksPerAnimalSpawns()); - } - if (getSettings().getTicksPerMonsterSpawns() > 0) { - w.setTicksPerSpawns(SpawnCategory.MONSTER, getSettings().getTicksPerMonsterSpawns()); - } - - } - - @Override - public WorldSettings getWorldSettings() { - return getSettings(); - } - - @Override - public @Nullable ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { - return chunkGenerator; - } - - @Override - public void saveWorldSettings() { - if (settings != null) { - configObject.saveConfigObject(settings); - } - } - - - @Override - public void saveDefaultConfig() - { - super.saveDefaultConfig(); - // Save default phases panel - this.saveResource("panels/phases_panel.yml", false); - } - - - /* (non-Javadoc) - * @see world.bentobox.bentobox.api.addons.Addon#allLoaded() - */ - @Override - public void allLoaded() { - // save settings. This will occur after all addons have loaded - this.saveWorldSettings(); - } - - /** - * @param i - island - * @return one block island data - */ - @NonNull - public OneBlockIslands getOneBlocksIsland(@NonNull Island i) { - return blockListener.getIsland(Objects.requireNonNull(i)); - } - - public OneBlocksManager getOneBlockManager() { - return oneBlockManager; - } - - /** - * @return the blockListener - */ - public BlockListener getBlockListener() { - return blockListener; - } - - /** - * Get the placeholder manager - * - * @return the phManager - */ - public PlaceholdersManager getPlaceholdersManager() { - return phManager; - } - - /** - * @return the holoListener - */ - public HoloListener getHoloListener() { - return holoListener; - } - - /** - * @return true if ItemsAdder is on the server - */ - public boolean hasItemsAdder() { - return hasItemsAdder; - } + private static final String NETHER = "_nether"; + private static final String THE_END = "_the_end"; + private boolean hasItemsAdder = false; + + // Settings + private Settings settings; + private ChunkGeneratorWorld chunkGenerator; + private final Config configObject = new Config<>(this, Settings.class); + private BlockListener blockListener; + private OneBlocksManager oneBlockManager; + private PlaceholdersManager phManager; + private HoloListener holoListener; + + @Override + public void onLoad() { + // Check if ItemsAdder exists, if yes register listener + if (Bukkit.getPluginManager().getPlugin("ItemsAdder") != null) { + registerListener(new ItemsAdderListener(this)); + hasItemsAdder = true; + } + // Save the default config from config.yml + saveDefaultConfig(); + // Load settings from config.yml. This will check if there are any issues with + // it too. + if (loadSettings()) { + // Chunk generator + chunkGenerator = settings.isUseOwnGenerator() ? null : new ChunkGeneratorWorld(this); + // Register commands + playerCommand = new PlayerCommand(this); + adminCommand = new AdminCommand(this); + } + } + + private boolean loadSettings() { + // Load settings again to get worlds + settings = configObject.loadConfigObject(); + if (settings == null) { + // Disable + logError("AOneBlock settings could not load! Addon disabled."); + setState(State.DISABLED); + return false; + } else { + // Save the settings + configObject.saveConfigObject(settings); + } + return true; + } + + @Override + public void onEnable() { + oneBlockManager = new OneBlocksManager(this); + if (loadData()) { + // Failed to load - don't register anything + return; + } + blockListener = new BlockListener(this); + registerListener(blockListener); + registerListener(new NoBlockHandler(this)); + registerListener(new BlockProtect(this)); + registerListener(new JoinLeaveListener(this)); + registerListener(new InfoListener(this)); + // Register placeholders + registerPlaceholders(); + + // Register request handlers + registerRequestHandler(new IslandStatsHandler(this)); + registerRequestHandler(new LocationStatsHandler(this)); + + // Register Holograms + holoListener = new HoloListener(this); + registerListener(holoListener); + } + + // Load phase data + public boolean loadData() { + try { + oneBlockManager.loadPhases(); + } catch (IOException e) { + // Disable + logError("AOneBlock settings could not load (oneblock.yml error)! Addon disabled."); + logError(e.getMessage()); + setState(State.DISABLED); + return true; + } + return false; + } + + private void registerPlaceholders() { + phManager = new PlaceholdersManager(this); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_phase", + phManager::getPhaseByLocation); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_count", + phManager::getCountByLocation); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_phase", phManager::getPhase); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_count", phManager::getCount); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_next_phase", + phManager::getNextPhaseByLocation); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_next_phase", phManager::getNextPhase); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_blocks_for_phase", + phManager::getPhaseBlocks); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_blocks_to_next_phase", + phManager::getNextPhaseBlocks); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_blocks_to_next_phase", + phManager::getNextPhaseBlocksByLocation); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_percent_done", + phManager::getPercentDone); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_percent_done", + phManager::getPercentDoneByLocation); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_done_scale", phManager::getDoneScale); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_done_scale", + phManager::getDoneScaleByLocation); + // Since 1.10 + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "visited_island_lifetime_count", + phManager::getLifetimeByLocation); + getPlugin().getPlaceholdersManager().registerPlaceholder(this, "my_island_lifetime_count", + phManager::getLifetime); + } + + @Override + public void onDisable() { + // save cache + blockListener.saveCache(); + + // Clear holograms + holoListener.clear(); + } + + @Override + public void onReload() { + // save cache + blockListener.saveCache(); + if (loadSettings()) { + log("Reloaded AOneBlock settings"); + } + } + + /** + * @return the settings + */ + public Settings getSettings() { + return settings; + } + + @Override + public void createWorlds() { + String worldName = settings.getWorldName().toLowerCase(); + if (getServer().getWorld(worldName) == null) { + log("Creating AOneBlock world ..."); + } + + // Create the world if it does not exist + islandWorld = getWorld(worldName, World.Environment.NORMAL, chunkGenerator); + // Make the nether if it does not exist + if (settings.isNetherGenerate()) { + if (getServer().getWorld(worldName + NETHER) == null) { + log("Creating AOneBlock's Nether..."); + } + netherWorld = settings.isNetherIslands() ? getWorld(worldName, World.Environment.NETHER, chunkGenerator) + : getWorld(worldName, World.Environment.NETHER, null); + } + // Make the end if it does not exist + if (settings.isEndGenerate()) { + if (getServer().getWorld(worldName + THE_END) == null) { + log("Creating AOneBlock's End World..."); + } + endWorld = settings.isEndIslands() ? getWorld(worldName, World.Environment.THE_END, chunkGenerator) + : getWorld(worldName, World.Environment.THE_END, null); + } + } + + /** + * Gets a world or generates a new world if it does not exist + * + * @param worldName2 - the overworld name + * @param env - the environment + * @param chunkGenerator2 - the chunk generator. If null then the + * generator will not be specified + * @return world loaded or generated + */ + private World getWorld(String worldName2, Environment env, ChunkGeneratorWorld chunkGenerator2) { + // Set world name + worldName2 = env.equals(World.Environment.NETHER) ? worldName2 + NETHER : worldName2; + worldName2 = env.equals(World.Environment.THE_END) ? worldName2 + THE_END : worldName2; + WorldCreator wc = WorldCreator.name(worldName2).type(WorldType.FLAT).environment(env); + World w = settings.isUseOwnGenerator() ? wc.createWorld() : wc.generator(chunkGenerator2).createWorld(); + // Set spawn rates + if (w != null) { + setSpawnRates(w); + } + return w; + + } + + private void setSpawnRates(World w) { + if (getSettings().getSpawnLimitMonsters() > 0) { + w.setSpawnLimit(SpawnCategory.MONSTER, getSettings().getSpawnLimitMonsters()); + } + if (getSettings().getSpawnLimitAmbient() > 0) { + w.setSpawnLimit(SpawnCategory.AMBIENT, getSettings().getSpawnLimitAmbient()); + } + if (getSettings().getSpawnLimitAnimals() > 0) { + w.setSpawnLimit(SpawnCategory.ANIMAL, getSettings().getSpawnLimitAnimals()); + } + if (getSettings().getSpawnLimitWaterAnimals() > 0) { + w.setSpawnLimit(SpawnCategory.WATER_ANIMAL, getSettings().getSpawnLimitWaterAnimals()); + } + if (getSettings().getTicksPerAnimalSpawns() > 0) { + w.setTicksPerSpawns(SpawnCategory.ANIMAL, getSettings().getTicksPerAnimalSpawns()); + } + if (getSettings().getTicksPerMonsterSpawns() > 0) { + w.setTicksPerSpawns(SpawnCategory.MONSTER, getSettings().getTicksPerMonsterSpawns()); + } + + } + + @Override + public WorldSettings getWorldSettings() { + return getSettings(); + } + + @Override + public @Nullable ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + return chunkGenerator; + } + + @Override + public void saveWorldSettings() { + if (settings != null) { + configObject.saveConfigObject(settings); + } + } + + @Override + public void saveDefaultConfig() { + super.saveDefaultConfig(); + // Save default phases panel + this.saveResource("panels/phases_panel.yml", false); + } + + /* + * (non-Javadoc) + * + * @see world.bentobox.bentobox.api.addons.Addon#allLoaded() + */ + @Override + public void allLoaded() { + // save settings. This will occur after all addons have loaded + this.saveWorldSettings(); + } + + /** + * @param i - island + * @return one block island data + */ + @NonNull + public OneBlockIslands getOneBlocksIsland(@NonNull Island i) { + return blockListener.getIsland(Objects.requireNonNull(i)); + } + + public OneBlocksManager getOneBlockManager() { + return oneBlockManager; + } + + /** + * @return the blockListener + */ + public BlockListener getBlockListener() { + return blockListener; + } + + /** + * Get the placeholder manager + * + * @return the phManager + */ + public PlaceholdersManager getPlaceholdersManager() { + return phManager; + } + + /** + * @return the holoListener + */ + public HoloListener getHoloListener() { + return holoListener; + } + + /** + * @return true if ItemsAdder is on the server + */ + public boolean hasItemsAdder() { + return hasItemsAdder; + } } diff --git a/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java b/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java index 5ea6dfa1..1a76a542 100644 --- a/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java +++ b/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java @@ -9,216 +9,242 @@ public class PlaceholdersManager { - private static final TreeMap SCALE; - static { - SCALE = new TreeMap<>(); - SCALE.put(0D, "&c╍╍╍╍╍╍╍╍"); - SCALE.put(12.5, "&a╍&c╍╍╍╍╍╍╍"); - SCALE.put(25.0, "&a╍╍&c╍╍╍╍╍╍"); - SCALE.put(37.5, "&a╍╍╍&c╍╍╍╍╍"); - SCALE.put(50D, "&a╍╍╍╍&c╍╍╍╍"); - SCALE.put(62.5, "&a╍╍╍╍╍&c╍╍╍"); - SCALE.put(75.0, "&a╍╍╍╍╍╍&c╍╍"); - SCALE.put(87.5, "&a╍╍╍╍╍╍╍&c╍"); - SCALE.put(100D, "&a╍╍╍╍╍╍╍╍"); - } - - private final AOneBlock addon; - - public PlaceholdersManager(AOneBlock addon) { - this.addon = addon; - } - - - /** - * Get phase by location of user - * @param user - user - * @return Phase name - */ - public String getPhaseByLocation(User user) { - if (user == null || user.getUniqueId() == null) return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland) - .map(OneBlockIslands::getPhaseName) - .orElse(""); - } - - /** - * Get block count by user location - * @param user - user - * @return String of count - */ - public String getCountByLocation(User user) { - if (user == null || user.getUniqueId() == null) return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland) - .map(OneBlockIslands::getBlockNumber) - .map(String::valueOf) - .orElse(""); - } - - /** - * Get user's island phase - * @param user - island owner or team member - * @return phase name - */ - public String getPhase(User user) { - if (user == null || user.getUniqueId() == null) return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - return i == null ? "" : addon.getOneBlocksIsland(i).getPhaseName(); - } - - /** - * Get island block count - * @param user island owner or team member - * @return string of block count - */ - public String getCount(User user) { - if (user == null || user.getUniqueId() == null) return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - return i == null ? "" : String.valueOf(addon.getOneBlocksIsland(i).getBlockNumber()); - } - - /** - * Get the next phase based on user's location - * @param user - user - * @return next phase - */ - public String getNextPhaseByLocation(User user) { - if (user == null || user.getUniqueId() == null) return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland) - .map(addon.getOneBlockManager()::getNextPhase) - .orElse(""); - } - - /** - * Get next island phase - * @param user island owner or team member - * @return next island phase - */ - public String getNextPhase(User user) { - if (user == null || user.getUniqueId() == null) return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - return i == null ? "" : addon.getOneBlockManager().getNextPhase(addon.getOneBlocksIsland(i)); - } - - /** - * Get how many blocks until next phase based on user's location - * @param user user - * @return string number of blocks - */ - public String getNextPhaseBlocksByLocation(User user) { - if (user == null || user.getUniqueId() == null) return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland) - .map(addon.getOneBlockManager()::getNextPhaseBlocks) - .map(num -> num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num)) - .orElse(""); - } - - /** - * Get how many blocks until the next island phase - * @param user owner or team member - * @return string number of blocks - */ - public String getNextPhaseBlocks(User user) { - if (user == null || user.getUniqueId() == null) return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - if (i == null) { - return ""; - } - int num = addon.getOneBlockManager().getNextPhaseBlocks(addon.getOneBlocksIsland(i)); - return num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num); - } - - /** - * Get percentage done of current phase by user's location - * @param user - user - * @return string percentage - */ - public String getPercentDoneByLocation(User user) { - if (user == null || user.getUniqueId() == null) return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland) - .map(addon.getOneBlockManager()::getPercentageDone) - .map(num -> Math.round(num) + "%") - .orElse(""); - } - - /** - * Get percentage done of user's island phase - * @param user owner or team member - * @return string percentage - */ - public String getPercentDone(User user) { - if (user == null || user.getUniqueId() == null) return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - if (i == null) { - return ""; - } - double num = addon.getOneBlockManager().getPercentageDone(addon.getOneBlocksIsland(i)); - return Math.round(num) + "%"; - } - - /** - * Get percentage done of phase as colored scale based on user's location - * @param user user - * @return colored scale - */ - public String getDoneScaleByLocation(User user) { - if (user == null || user.getUniqueId() == null) return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland) - .map(addon.getOneBlockManager()::getPercentageDone) - .map(num -> SCALE.floorEntry(num).getValue()) - .map(s -> s.replace("╍", addon.getSettings().getPercentCompleteSymbol())) - .orElse(""); - } - - /** - * Get percentage done of phase as colored scale - * @param user owner or team member - * @return colored scale - */ - public String getDoneScale(User user) { - if (user == null || user.getUniqueId() == null) return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - if (i == null) { - return ""; - } - double num = addon.getOneBlockManager().getPercentageDone(addon.getOneBlocksIsland(i)); - return SCALE.floorEntry(num).getValue().replace("╍", addon.getSettings().getPercentCompleteSymbol()); - } - - /** - * Get island Lifetime count - * @param user island owner or team member - * @return string of Lifetime count - */ - public String getLifetime(User user) - { - if (user == null || user.getUniqueId() == null) return ""; - - Island island = this.addon.getIslands().getIsland(this.addon.getOverWorld(), user); - - return island == null ? "" : String.valueOf(this.addon.getOneBlocksIsland(island).getLifetime()); - } - - - /** - * Get Lifetime count by user location - * @param user - user - * @return String of Lifetime - */ - public String getLifetimeByLocation(User user) - { - if (user == null || user.getUniqueId() == null) return ""; - - return this.addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())). - map(this.addon::getOneBlocksIsland). - map(OneBlockIslands::getLifetime). - map(String::valueOf). - orElse(""); - } + private static final TreeMap SCALE; + static { + SCALE = new TreeMap<>(); + SCALE.put(0D, "&c╍╍╍╍╍╍╍╍"); + SCALE.put(12.5, "&a╍&c╍╍╍╍╍╍╍"); + SCALE.put(25.0, "&a╍╍&c╍╍╍╍╍╍"); + SCALE.put(37.5, "&a╍╍╍&c╍╍╍╍╍"); + SCALE.put(50D, "&a╍╍╍╍&c╍╍╍╍"); + SCALE.put(62.5, "&a╍╍╍╍╍&c╍╍╍"); + SCALE.put(75.0, "&a╍╍╍╍╍╍&c╍╍"); + SCALE.put(87.5, "&a╍╍╍╍╍╍╍&c╍"); + SCALE.put(100D, "&a╍╍╍╍╍╍╍╍"); + } + + private final AOneBlock addon; + + public PlaceholdersManager(AOneBlock addon) { + this.addon = addon; + } + + /** + * Get phase by location of user + * + * @param user - user + * @return Phase name + */ + public String getPhaseByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(OneBlockIslands::getPhaseName).orElse(""); + } + + /** + * Get block count by user location + * + * @param user - user + * @return String of count + */ + public String getCountByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(OneBlockIslands::getBlockNumber).map(String::valueOf).orElse(""); + } + + /** + * Get user's island phase + * + * @param user - island owner or team member + * @return phase name + */ + public String getPhase(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + return i == null ? "" : addon.getOneBlocksIsland(i).getPhaseName(); + } + + /** + * Get island block count + * + * @param user island owner or team member + * @return string of block count + */ + public String getCount(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + return i == null ? "" : String.valueOf(addon.getOneBlocksIsland(i).getBlockNumber()); + } + + /** + * Get the next phase based on user's location + * + * @param user - user + * @return next phase + */ + public String getNextPhaseByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getNextPhase).orElse(""); + } + + /** + * Get next island phase + * + * @param user island owner or team member + * @return next island phase + */ + public String getNextPhase(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + return i == null ? "" : addon.getOneBlockManager().getNextPhase(addon.getOneBlocksIsland(i)); + } + + /** + * Get how many blocks until next phase based on user's location + * + * @param user user + * @return string number of blocks + */ + public String getNextPhaseBlocksByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getNextPhaseBlocks) + .map(num -> num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num)) + .orElse(""); + } + + /** + * Get how many blocks until the next island phase + * + * @param user owner or team member + * @return string number of blocks + */ + public String getNextPhaseBlocks(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + if (i == null) { + return ""; + } + int num = addon.getOneBlockManager().getNextPhaseBlocks(addon.getOneBlocksIsland(i)); + return num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num); + } + + /** + * Get how many blocks for this phase + * + * @param user owner or team member + * @return string number of blocks + */ + public String getPhaseBlocks(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + if (i == null) { + return ""; + } + int num = addon.getOneBlockManager().getNextPhaseBlocks(addon.getOneBlocksIsland(i)); + return num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num); + } + + /** + * Get percentage done of current phase by user's location + * + * @param user - user + * @return string percentage + */ + public String getPercentDoneByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getPercentageDone) + .map(num -> Math.round(num) + "%").orElse(""); + } + + /** + * Get percentage done of user's island phase + * + * @param user owner or team member + * @return string percentage + */ + public String getPercentDone(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + if (i == null) { + return ""; + } + double num = addon.getOneBlockManager().getPercentageDone(addon.getOneBlocksIsland(i)); + return Math.round(num) + "%"; + } + + /** + * Get percentage done of phase as colored scale based on user's location + * + * @param user user + * @return colored scale + */ + public String getDoneScaleByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getPercentageDone) + .map(num -> SCALE.floorEntry(num).getValue()) + .map(s -> s.replace("╍", addon.getSettings().getPercentCompleteSymbol())).orElse(""); + } + + /** + * Get percentage done of phase as colored scale + * + * @param user owner or team member + * @return colored scale + */ + public String getDoneScale(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + if (i == null) { + return ""; + } + double num = addon.getOneBlockManager().getPercentageDone(addon.getOneBlocksIsland(i)); + return SCALE.floorEntry(num).getValue().replace("╍", addon.getSettings().getPercentCompleteSymbol()); + } + + /** + * Get island Lifetime count + * + * @param user island owner or team member + * @return string of Lifetime count + */ + public String getLifetime(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + + Island island = this.addon.getIslands().getIsland(this.addon.getOverWorld(), user); + + return island == null ? "" : String.valueOf(this.addon.getOneBlocksIsland(island).getLifetime()); + } + + /** + * Get Lifetime count by user location + * + * @param user - user + * @return String of Lifetime + */ + public String getLifetimeByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + + return this.addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(this.addon::getOneBlocksIsland).map(OneBlockIslands::getLifetime).map(String::valueOf).orElse(""); + } } diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java index e369b6bb..361573ec 100644 --- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java +++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java @@ -42,779 +42,817 @@ /** * Provides a manager for all phases + * * @author tastybento * */ public class OneBlocksManager { - private static final String ONE_BLOCKS_YML = "oneblocks.yml"; - private static final String NAME = "name"; - private static final String BIOME = "biome"; - private static final String FIRST_BLOCK = "firstBlock"; - private static final String ICON = "icon"; - private static final String FIXED_BLOCKS = "fixedBlocks"; - private static final String HOLOGRAMS = "holograms"; - private static final String CHESTS = "chests"; - private static final String RARITY = "rarity"; - private static final String CONTENTS = "contents"; - private static final String MOBS = "mobs"; - private static final String BLOCKS = "blocks"; - private static final String PHASES = "phases"; - private static final String GOTO_BLOCK = "gotoBlock"; - private static final String START_COMMANDS = "start-commands"; - private static final String END_COMMANDS = "end-commands"; - private static final String END_COMMANDS_FIRST_TIME = "end-commands-first-time"; - private static final String REQUIREMENTS = "requirements"; - private final AOneBlock addon; - private TreeMap blockProbs; - - /** - * @param addon - addon - */ - public OneBlocksManager(AOneBlock addon) { - this.addon = addon; - // Initialize block probabilities - blockProbs = new TreeMap<>(); - } - - /** - * Loads the game phases - * - * @throws IOException - if config file has bad syntax or migration fails - */ - public void loadPhases() throws IOException { - // Clear block probabilities - blockProbs = new TreeMap<>(); - // Check for folder - File check = new File(addon.getDataFolder(), PHASES); - if (check.mkdirs()) { - addon.log(check.getAbsolutePath() + " does not exist, made folder."); - // Check for oneblock.yml - File oneblockFile = new File(addon.getDataFolder(), ONE_BLOCKS_YML); - if (oneblockFile.exists()) { - // Migrate to new folders - File renamedFile = new File(check, ONE_BLOCKS_YML); - Files.move(oneblockFile, renamedFile); - loadPhase(renamedFile); - this.saveOneBlockConfig(); - java.nio.file.Files.delete(oneblockFile.toPath()); - java.nio.file.Files.delete(renamedFile.toPath()); - blockProbs.clear(); - } else { - // Copy files from JAR - copyPhasesFromAddonJar(check); - } - } - // Get files in folder - // Filter for files ending with .yml - FilenameFilter ymlFilter = (dir, name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(".yml"); - for (File phaseFile : Objects.requireNonNull(check.listFiles(ymlFilter))) { - loadPhase(phaseFile); - } - } - - /** - * Copies phase files from the addon jar to the file system - * - * @param file - the file to copy - */ - void copyPhasesFromAddonJar(File file) { - try (JarFile jar = new JarFile(addon.getFile())) { - // Obtain any locale files, save them and update - Util.listJarFiles(jar, PHASES, ".yml").forEach(lf -> addon.saveResource(lf, file, false, true)); - } catch (Exception e) { - addon.logError(e.getMessage()); - } - } - - private void loadPhase(File phaseFile) throws IOException { - addon.log("Loading " + phaseFile.getName()); - // Load the config file - YamlConfiguration oneBlocks = new YamlConfiguration(); - try { - oneBlocks.load(phaseFile); - } catch (Exception e) { - addon.logError(e.getMessage()); - return; - } - for (String blockNumber : oneBlocks.getKeys(false)) { - Integer blockNum = Integer.valueOf(blockNumber); - OneBlockPhase obPhase = blockProbs.computeIfAbsent(blockNum, k -> new OneBlockPhase(blockNumber)); - // Get config Section - ConfigurationSection phase = oneBlocks.getConfigurationSection(blockNumber); - // goto - if (phase.contains(GOTO_BLOCK)) { - obPhase.setGotoBlock(phase.getInt(GOTO_BLOCK, 0)); - continue; - } - initBlock(blockNumber, obPhase, phase); - // Blocks - addBlocks(obPhase, phase); - // Mobs - addMobs(obPhase, phase); - // Chests - addChests(obPhase, phase); - // Commands - addCommands(obPhase, phase); - // Requirements - addRequirements(obPhase, phase); - // Add to the map - blockProbs.put(blockNum, obPhase); - } - } - - /** - * Load in the phase's init - * @param blockNumber string representation of this phase's block number - * @param obPhase OneBlockPhase - * @param phase configuration section being read - * @throws IOException if there's an error in the config file - */ - void initBlock(String blockNumber, OneBlockPhase obPhase, ConfigurationSection phase) throws IOException { - if (phase.contains(NAME, true)) { - if (obPhase.getPhaseName() != null) { - throw new IOException("Block " + blockNumber + ": Phase name trying to be set to " + phase.getString(NAME) + " but already set to " + obPhase.getPhaseName() + ". Duplicate phase file?"); - } - // name - obPhase.setPhaseName(phase.getString(NAME, blockNumber)); - } - // biome - if (phase.contains(BIOME, true)) { - if (obPhase.getPhaseBiome() != null) { - throw new IOException("Block " + blockNumber + ": Biome trying to be set to " + phase.getString(BIOME) + " but already set to " + obPhase.getPhaseBiome() + " Duplicate phase file?"); - } - obPhase.setPhaseBiome(getBiome(phase.getString(BIOME))); - } - // First block - if (phase.contains(FIRST_BLOCK)) { - if (obPhase.getFirstBlock() != null) { - throw new IOException("Block " + blockNumber + ": First block trying to be set to " + phase.getString(FIRST_BLOCK) + " but already set to " + obPhase.getFirstBlock() + " Duplicate phase file?"); - } - addFirstBlock(obPhase, phase.getString(FIRST_BLOCK)); - } - // Icon block - if (phase.contains(ICON)) { - ItemStack icon = ItemParser.parse(phase.getString(ICON)); - - if (icon == null) { - throw new IOException("ItemParser failed to parse icon: '" + phase.getString(ICON) + "' for phase " + obPhase.getFirstBlock() + ". Can you check if it is correct?"); - } - - obPhase.setIconBlock(icon); - } - // First blocks - if (phase.contains(FIXED_BLOCKS)) { - if (!obPhase.getFixedBlocks().isEmpty()) { - throw new IOException("Block " + blockNumber + ": Fixed blocks trying to be set to " + phase.getString(FIXED_BLOCKS) + " but already set to " + obPhase.getFixedBlocks() + " Duplicate phase file?"); - } - addFixedBlocks(obPhase, phase.getConfigurationSection(FIXED_BLOCKS)); - } - - if (phase.contains(HOLOGRAMS)) { - if (!obPhase.getHologramLines().isEmpty()) { - throw new IOException("Block " + blockNumber + ": Hologram Lines trying to be set to " + phase.getString(HOLOGRAMS) + " but already set to " + obPhase.getHologramLines() + " Duplicate phase file?"); - } - addHologramLines(obPhase, phase.getConfigurationSection(HOLOGRAMS)); - } - } - - private void addFixedBlocks(OneBlockPhase obPhase, ConfigurationSection fb) { - if (fb == null) { - return; - } - Map result = new HashMap<>(); - for (String key : fb.getKeys(false)) { - if (!NumberUtils.isNumber(key)) { - addon.logError("Fixed block key must be an integer. " + key); - continue; - } - int k = Integer.parseInt(key); - if (fb.isConfigurationSection(key)) { - Map map = fb.getConfigurationSection(key).getValues(false); - Optional customBlock = OneBlockCustomBlockCreator.create(map); - if (customBlock.isPresent()) { - result.put(k, new OneBlockObject(customBlock.get(), 0)); - } else { - addon.logError("Fixed block key " + key + " material is not a valid custom block. Ignoring."); - } - } else { - String mat = fb.getString(key); - if (mat == null) { - continue; - } - - Optional customBlock = OneBlockCustomBlockCreator.create(mat); - if (customBlock.isPresent()) { - result.put(k, new OneBlockObject(customBlock.get(), 0)); - } else { - Material m = Material.matchMaterial(mat); - if (m != null && m.isBlock()) { - result.put(k, new OneBlockObject(m, 0)); - } else { - addon.logError("Fixed block key " + key + " material is invalid or not a block. Ignoring."); - } - } - } - } - // Set the first block if it exists - if (result.containsKey(0)) { - obPhase.setFirstBlock(result.get(0)); - } - // Store the remainder - obPhase.setFixedBlocks(result); - - } - - private void addHologramLines(OneBlockPhase obPhase, ConfigurationSection fb) { - if (fb == null) return; - Map result = new HashMap<>(); - for (String key : fb.getKeys(false)) { - if (!NumberUtils.isNumber(key)) { - addon.logError("Fixed block key must be an integer. " + key); - continue; - } - int k = Integer.parseInt(key); - String line = fb.getString(key); - if (line != null) { - result.put(k, line); - } - } - // Set Hologram Lines - obPhase.setHologramLines(result); - - } - - private Biome getBiome(String string) { - if (string == null) { - return Biome.PLAINS; - } - if (Enums.getIfPresent(Biome.class, string).isPresent()) { - return Biome.valueOf(string); - } - // Special case for nether - if (string.equals("NETHER") || string.equals("NETHER_WASTES")) { - return Enums.getIfPresent(Biome.class, "NETHER") - .or(Enums.getIfPresent(Biome.class, "NETHER_WASTES").or(Biome.PLAINS)); - } - addon.logError("Biome " + string.toUpperCase() + " is invalid! Use one of these..."); - addon.logError(Arrays.stream(Biome.values()).map(Biome::name).collect(Collectors.joining(","))); - return Biome.PLAINS; - } - - void addFirstBlock(OneBlockPhase obPhase, @Nullable String material) { - if (material == null) { - return; - } - Material m = Material.matchMaterial(material); - if (m == null || !m.isBlock()) { - addon.logError("Bad firstBlock material: " + material); - } else { - obPhase.setFirstBlock(new OneBlockObject(m, 0)); - } - } - - void addCommands(OneBlockPhase obPhase, ConfigurationSection phase) { - if (phase.contains(START_COMMANDS)) { - obPhase.setStartCommands(phase.getStringList(START_COMMANDS)); - } - if (phase.contains(END_COMMANDS)) { - obPhase.setEndCommands(phase.getStringList(END_COMMANDS)); - } - if (phase.contains(END_COMMANDS_FIRST_TIME)) { - obPhase.setFirstTimeEndCommands(phase.getStringList(END_COMMANDS_FIRST_TIME)); - } - } - - void addRequirements(OneBlockPhase obPhase, ConfigurationSection phase) { - List reqList = new ArrayList<>(); - if (!phase.isConfigurationSection(REQUIREMENTS)) { - return; - } - ConfigurationSection reqs = phase.getConfigurationSection(REQUIREMENTS); - for (ReqType key : Requirement.ReqType.values()) { - if (reqs.contains(key.getKey())) { - Requirement r; - if (key.getClazz().equals(Double.class)) { - r = new Requirement(key, reqs.getDouble(key.getKey())); - } else if (key.getClazz().equals(Long.class)) { - r = new Requirement(key, reqs.getLong(key.getKey())); - } else { - r = new Requirement(key, reqs.getString(key.getKey())); - } - reqList.add(r); - } - } - obPhase.setRequirements(reqList); - } - - void addChests(OneBlockPhase obPhase, ConfigurationSection phase) throws IOException { - if (!phase.isConfigurationSection(CHESTS)) { - return; - } - if (!obPhase.getChests().isEmpty()) { - throw new IOException(obPhase.getPhaseName() + ": Chests cannot be set more than once. Duplicate file?"); - } - ConfigurationSection chests = phase.getConfigurationSection(CHESTS); - for (String chestId : chests.getKeys(false)) { - ConfigurationSection chest = chests.getConfigurationSection(chestId); - Rarity rarity = Rarity.COMMON; - try { - rarity = OneBlockObject.Rarity.valueOf(chest.getString(RARITY, "COMMON").toUpperCase()); - } catch (Exception e) { - addon.logError( - "Rarity value of " + chest.getString(RARITY, "UNKNOWN") + " is invalid! Use one of these..."); - addon.logError(Arrays.stream(Rarity.values()).map(Rarity::name).collect(Collectors.joining(","))); - rarity = Rarity.COMMON; - } - Map items = new HashMap<>(); - ConfigurationSection contents = chest.getConfigurationSection(CONTENTS); - if (contents != null) { - for (String index : contents.getKeys(false)) { - int slot = Integer.parseInt(index); - ItemStack item = contents.getItemStack(index); - if (item != null) { - items.put(slot, item); - } - } - } - obPhase.addChest(items, rarity); - } - } - - void addMobs(OneBlockPhase obPhase, ConfigurationSection phase) throws IOException { - if (!phase.isConfigurationSection(MOBS)) { - return; - } - if (!obPhase.getMobs().isEmpty()) { - throw new IOException(obPhase.getPhaseName() + ": Mobs cannot be set more than once. Duplicate file?"); - } - ConfigurationSection mobs = phase.getConfigurationSection(MOBS); - for (String entity : mobs.getKeys(false)) { - String name = entity.toUpperCase(Locale.ENGLISH); - EntityType et = null; - // Pig zombie handling - if (name.equals("PIG_ZOMBIE") || name.equals("ZOMBIFIED_PIGLIN")) { - et = Enums.getIfPresent(EntityType.class, "ZOMBIFIED_PIGLIN") - .or(Enums.getIfPresent(EntityType.class, "PIG_ZOMBIE").or(EntityType.PIG)); - } else { - et = Enums.getIfPresent(EntityType.class, name).orNull(); - } - if (et == null) { - // Does not exist - addon.logError("Bad entity type in " + obPhase.getPhaseName() + ": " + entity); - addon.logError("Try one of these..."); - addon.logError(Arrays.stream(EntityType.values()).filter(EntityType::isSpawnable) - .filter(EntityType::isAlive).map(EntityType::name).collect(Collectors.joining(","))); - return; - } - if (et.isSpawnable() && et.isAlive()) { - if (mobs.getInt(entity) > 0) { - obPhase.addMob(et, mobs.getInt(entity)); - } else { - addon.logWarning("Bad entity weight for " + obPhase.getPhaseName() + ": " + entity + ". Must be positive number above 1."); - } - } else { - addon.logError("Entity type is not spawnable " + obPhase.getPhaseName() + ": " + entity); - } - } - } - - void addBlocks(OneBlockPhase obPhase, ConfigurationSection phase) { - if (phase.isConfigurationSection(BLOCKS)) { - ConfigurationSection blocks = phase.getConfigurationSection(BLOCKS); - for (String material : blocks.getKeys(false)) { - if (Material.getMaterial(material) != null) { - addMaterial(obPhase, material, Objects.toString(blocks.get(material))); - } else { - if (addon.hasItemsAdder()) { - CustomBlock block = CustomBlock.getInstance(material); - if (block != null) { - addItemsAdderBlock(obPhase, material, Objects.toString(blocks.get(material))); - } else if (ItemsAdder.getAllItems() != null){ - if (ItemsAdder.getAllItems().size() != 0) { - addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material); - } - } - } else { - addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material); - } - } - } - } else if (phase.isList(BLOCKS)) { - List> blocks = phase.getMapList(BLOCKS); - for (Map map : blocks) { - if (map.size() == 1) { - Map.Entry entry = map.entrySet().iterator().next(); - if (addMaterial(obPhase, Objects.toString(entry.getKey()), Objects.toString(entry.getValue()))) { - continue; - } - } - - int probability = Integer.parseInt(Objects.toString(map.get("probability"), "0")); - Optional customBlock = OneBlockCustomBlockCreator.create(map); - if (customBlock.isPresent()) { - obPhase.addCustomBlock(customBlock.get(), probability); - } else { - addon.logError("Bad custom block in " + obPhase.getPhaseName() + ": " + map); - } - } - } - } - - private boolean addMaterial(OneBlockPhase obPhase, String material, String probability) { - int prob; - try { - prob = Integer.parseInt(probability); - } catch (Exception e) { - return false; - } - - if (prob < 1) { - addon.logWarning("Bad item weight for " + obPhase.getPhaseName() + ": " + material + ". Must be positive number above 1."); - return false; - } - - // Register if the material is a valid custom block and can be created from the short creator from OneBlockCustomBlockCreator - Optional optionalCustomBlock = OneBlockCustomBlockCreator.create(material); - if (optionalCustomBlock.isPresent()) { - obPhase.addCustomBlock(optionalCustomBlock.get(), prob); - return true; - } - - // Otherwise, register the material as a block - Material m = Material.matchMaterial(material); - if (m == null || !m.isBlock()) { - addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material); - return false; - } - obPhase.addBlock(m, prob); - return true; - } - - private void addItemsAdderBlock(OneBlockPhase obPhase, String block, String probability) { - int prob; - try { - prob = Integer.parseInt(probability); - if (prob < 1) { - addon.logWarning("Bad item weight for " + obPhase.getPhaseName() + ": " + block + ". Must be positive number above 1."); - } else { - obPhase.addItemsAdderCustomBlock(block, prob); - } - } catch (Exception e) { - addon.logError("Bad item weight for " + obPhase.getPhaseName() + ": " + block + ". Must be a number."); - } - - } - - /** - * Return the current phase for the block count - * - * @param blockCount - number of blocks mined - * @return the one block phase based on blockCount or null if there is none - */ - @Nullable - public OneBlockPhase getPhase(int blockCount) { - Entry en = blockProbs.floorEntry(blockCount); - return en != null ? en.getValue() : null; - } - - /** - * @return list of phase names with spaces replaced by underscore so they are - * one word - */ - public List getPhaseList() { - return blockProbs.values().stream().map(OneBlockPhase::getPhaseName).filter(Objects::nonNull) - .map(n -> n.replace(" ", "_")).collect(Collectors.toList()); - } - - /** - * @return the blockProbs - */ - public NavigableMap getBlockProbs() { - return blockProbs; - } - - /** - * Get phase by name. Name should have any spaces converted to underscores. Case - * insensitive. - * - * @param name - name to search - * @return optional OneBlockPhase - */ - public Optional getPhase(String name) { - return blockProbs.values().stream() - .filter(p -> p.getPhaseName() != null - && (p.getPhaseName().equalsIgnoreCase(name) - || p.getPhaseName().replace(" ", "_").equalsIgnoreCase(name))) - .findFirst(); - } - - /** - * Save all the phases in memory to disk. - * - * @return true if saved - */ - - public boolean saveOneBlockConfig() { - // Go through each phase - boolean success = true; - for (OneBlockPhase p : blockProbs.values()) { - success = savePhase(p); - } - return success; - } - - /** - * Save a phase - * @param p OneBlockPhase - * @return true if successfully saved - */ - public boolean savePhase(OneBlockPhase p) { - if (!saveMainPhase(p)) { - // Failure - return false; - } - // No chests in goto phases - if (p.isGotoPhase()) { - // Done - return true; - } - return saveChestPhase(p); - } - - private boolean saveMainPhase(OneBlockPhase p) { - YamlConfiguration oneBlocks = new YamlConfiguration(); - ConfigurationSection phSec = oneBlocks.createSection(p.getBlockNumber()); - // Check for a goto block - if (p.isGotoPhase()) { - phSec.set(GOTO_BLOCK, p.getGotoBlock()); - } else { - phSec.set(NAME, p.getPhaseName()); - if (p.getIconBlock() != null) { - phSec.set(ICON, p.getIconBlock().getType().name()); - } - if (p.getFirstBlock() != null) { - phSec.set(FIRST_BLOCK, p.getFirstBlock().getMaterial().name()); - } - if (p.getPhaseBiome() != null) { - phSec.set(BIOME, p.getPhaseBiome().name()); - } - saveBlocks(phSec, p); - saveEntities(phSec, p); - saveHolos(phSec, p); - saveCommands(phSec, p); - } - try { - // Save - File phaseFile = new File(addon.getDataFolder() + File.separator + PHASES, - getPhaseFileName(p) + ".yml"); - oneBlocks.save(phaseFile); - } catch (IOException e) { - addon.logError("Could not save phase " + p.getPhaseName() + " " + e.getMessage()); - return false; - } - return true; - } - - private void saveCommands(ConfigurationSection phSec, OneBlockPhase p) { - phSec.set(START_COMMANDS, p.getStartCommands()); - phSec.set(END_COMMANDS, p.getEndCommands()); - - } - - private void saveHolos(ConfigurationSection phSec, OneBlockPhase p) { - if (p.getHologramLines() == null) return; - ConfigurationSection holos = phSec.createSection(HOLOGRAMS); - p.getHologramLines().forEach((k, v) -> holos.set(String.valueOf(k), v)); - } - - private boolean saveChestPhase(OneBlockPhase p) { - YamlConfiguration oneBlocks = new YamlConfiguration(); - ConfigurationSection phSec = oneBlocks.createSection(p.getBlockNumber()); - saveChests(phSec, p); - try { - // Save - File phaseFile = new File(addon.getDataFolder() + File.separator + PHASES, - getPhaseFileName(p) + "_chests.yml"); - oneBlocks.save(phaseFile); - } catch (IOException e) { - addon.logError("Could not save chest phase " + p.getPhaseName() + " " + e.getMessage()); - return false; - } - return true; - } - - private String getPhaseFileName(OneBlockPhase p) { - if (p.isGotoPhase()) { - return p.getBlockNumber() + "_goto_" + p.getGotoBlock(); - } - return p.getBlockNumber() + "_" + (p.getPhaseName() == null ? "" : p.getPhaseName().toLowerCase().replace(' ', '_')); - } - - private void saveChests(ConfigurationSection phSec, OneBlockPhase phase) { - ConfigurationSection chests = phSec.createSection(CHESTS); - int index = 1; - for (OneBlockObject chest : phase.getChests()) { - ConfigurationSection c = chests.createSection(String.valueOf(index++)); - c.set(CONTENTS, chest.getChest()); - c.set(RARITY, chest.getRarity().name()); - } - - } - - private void saveEntities(ConfigurationSection phSec, OneBlockPhase phase) { - ConfigurationSection mobs = phSec.createSection(MOBS); - phase.getMobs().forEach((k, v) -> mobs.set(k.name(), v)); - } - - private void saveBlocks(ConfigurationSection phSec, OneBlockPhase phase) { - ConfigurationSection fixedBlocks = phSec.createSection(FIXED_BLOCKS); - phase.getFixedBlocks().forEach((k, v) -> fixedBlocks.set(String.valueOf(k), v.getMaterial().name())); - ConfigurationSection blocks = phSec.createSection(BLOCKS); - phase.getBlocks().forEach((k, v) -> blocks.set(k.name(), v)); - - } - - /** - * Get the phase after this one - * - * @param phase - one block phase - * @return next phase or null if there isn't one - */ - @SuppressWarnings("WrapperTypeMayBePrimitive") - @Nullable - public OneBlockPhase getNextPhase(@NonNull OneBlockPhase phase) { - // These are Integer objects because GSON can yield nulls if they do not exist - Integer blockNum = phase.getBlockNumberValue(); - Integer nextKey = blockProbs.ceilingKey(blockNum + 1); - return nextKey != null ? this.getPhase(nextKey) : null; - } - - /** - * Get the number of blocks until the next phase after this one - * - * @param obi - one block island - * @return number of blocks to the next phase. If there is no phase after -1 is - * returned. - */ - public int getNextPhaseBlocks(@NonNull OneBlockIslands obi) { - Integer blockNum = obi.getBlockNumber(); - Integer nextKey = blockProbs.ceilingKey(blockNum + 1); - if (nextKey == null) { - return -1; - } - OneBlockPhase nextPhase = this.getPhase(nextKey); - return nextPhase == null ? -1 : (nextPhase.getBlockNumberValue() - obi.getBlockNumber()); - } - - /** - * Get the percentage done of this phase - * - * @param obi - one block island - * @return percentage done. If there is no next phase then return 0 - */ - public double getPercentageDone(@NonNull OneBlockIslands obi) { - int blockNum = obi.getBlockNumber(); - OneBlockPhase thisPhase = this.getPhase(blockNum); - if (thisPhase == null) { - return 0; - } - Integer nextKey = blockProbs.ceilingKey(blockNum + 1); - if (nextKey == null) { - return 0; - } - OneBlockPhase nextPhase = this.getPhase(nextKey); - if (nextPhase == null) { - return 0; - } - int phaseSize = nextPhase.getBlockNumberValue() - thisPhase.getBlockNumberValue(); - return 100 - (100 * (double) (nextPhase.getBlockNumberValue() - obi.getBlockNumber()) / phaseSize); - } - - public void getProbs(OneBlockPhase phase) { - // Find the phase after this one - Integer blockNum = Integer.valueOf(phase.getBlockNumber()); - Integer nextKey = blockProbs.ceilingKey(blockNum + 1); - if (nextKey != null) { - // This is the size of the phase in blocks - int phaseSize = nextKey - blockNum; - int blockTotal = phase.getBlockTotal(); - int likelyChestTotal = 0; - double totalBlocks = 0; - // Now calculate the relative block probability - for (Entry en : phase.getBlocks().entrySet()) { - double chance = (double) en.getValue() / blockTotal; - double likelyNumberGenerated = chance * phaseSize; - totalBlocks += likelyNumberGenerated; - String report = en.getKey() + " likely generated = " + Math.round(likelyNumberGenerated) + " = " - + Math.round(likelyNumberGenerated * 100 / phaseSize) + "%"; - if (likelyNumberGenerated < 1) { - addon.logWarning(report); - } else { - addon.log(report); - } - if (en.getKey().equals(Material.CHEST)) { - likelyChestTotal = (int) Math.round(likelyNumberGenerated); - } - } - addon.log("Total blocks generated = " + totalBlocks); - // Get the specific chest probability - if (likelyChestTotal == 0) { - addon.logWarning("No chests will be generated"); - return; - } - addon.log("**** A total of " + likelyChestTotal + " chests will be generated ****"); - // Now calculate chest chances - double lastChance = 0; - for (Entry en : OneBlockPhase.CHEST_CHANCES.entrySet()) { - // Get the number of chests in this rarity group - int num = phase.getChestsMap().getOrDefault(en.getValue(), Collections.emptyList()).size(); - double likelyNumberGenerated = (en.getKey() - lastChance) * likelyChestTotal; - lastChance = en.getKey(); - String report = num + " " + en.getValue() + " chests in phase. Likely number generated = " - + Math.round(likelyNumberGenerated); - if (num > 0 && likelyNumberGenerated < 1) { - addon.logWarning(report); - } else { - addon.log(report); - } - - } - // Mobs - addon.log("-=-=-=-= Mobs -=-=-=-=-"); - double totalMobs = 0; - // Now calculate the relative block probability - for (Entry en : phase.getMobs().entrySet()) { - double chance = (double) en.getValue() / phase.getTotal(); - double likelyNumberGenerated = chance * phaseSize; - totalMobs += likelyNumberGenerated; - String report = en.getKey() + " likely generated = " + Math.round(likelyNumberGenerated) + " = " - + Math.round(likelyNumberGenerated * 100 / phaseSize) + "%"; - if (likelyNumberGenerated < 1) { - addon.logWarning(report); - } else { - addon.log(report); - } - } - addon.log("**** A total of " + Math.round(totalMobs) + " mobs will likely be generated ****"); - } - } - - /** - * Get all the probs for each phases and log to console - */ - public void getAllProbs() { - blockProbs.values().forEach(this::getProbs); - } - - /** - * Get the next phase name - * - * @param obi - one block island - * @return next phase name or an empty string - */ - public String getNextPhase(@NonNull OneBlockIslands obi) { - return getPhase(obi.getPhaseName()).map(this::getNextPhase) // Next phase or null - .filter(Objects::nonNull).map(OneBlockPhase::getPhaseName).orElse(""); - } + private static final String ONE_BLOCKS_YML = "oneblocks.yml"; + private static final String NAME = "name"; + private static final String BIOME = "biome"; + private static final String FIRST_BLOCK = "firstBlock"; + private static final String ICON = "icon"; + private static final String FIXED_BLOCKS = "fixedBlocks"; + private static final String HOLOGRAMS = "holograms"; + private static final String CHESTS = "chests"; + private static final String RARITY = "rarity"; + private static final String CONTENTS = "contents"; + private static final String MOBS = "mobs"; + private static final String BLOCKS = "blocks"; + private static final String PHASES = "phases"; + private static final String GOTO_BLOCK = "gotoBlock"; + private static final String START_COMMANDS = "start-commands"; + private static final String END_COMMANDS = "end-commands"; + private static final String END_COMMANDS_FIRST_TIME = "end-commands-first-time"; + private static final String REQUIREMENTS = "requirements"; + private final AOneBlock addon; + private TreeMap blockProbs; + + /** + * @param addon - addon + */ + public OneBlocksManager(AOneBlock addon) { + this.addon = addon; + // Initialize block probabilities + blockProbs = new TreeMap<>(); + } + + /** + * Loads the game phases + * + * @throws IOException - if config file has bad syntax or migration fails + */ + public void loadPhases() throws IOException { + // Clear block probabilities + blockProbs = new TreeMap<>(); + // Check for folder + File check = new File(addon.getDataFolder(), PHASES); + if (check.mkdirs()) { + addon.log(check.getAbsolutePath() + " does not exist, made folder."); + // Check for oneblock.yml + File oneblockFile = new File(addon.getDataFolder(), ONE_BLOCKS_YML); + if (oneblockFile.exists()) { + // Migrate to new folders + File renamedFile = new File(check, ONE_BLOCKS_YML); + Files.move(oneblockFile, renamedFile); + loadPhase(renamedFile); + this.saveOneBlockConfig(); + java.nio.file.Files.delete(oneblockFile.toPath()); + java.nio.file.Files.delete(renamedFile.toPath()); + blockProbs.clear(); + } else { + // Copy files from JAR + copyPhasesFromAddonJar(check); + } + } + // Get files in folder + // Filter for files ending with .yml + FilenameFilter ymlFilter = (dir, name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(".yml"); + for (File phaseFile : Objects.requireNonNull(check.listFiles(ymlFilter))) { + loadPhase(phaseFile); + } + } + + /** + * Copies phase files from the addon jar to the file system + * + * @param file - the file to copy + */ + void copyPhasesFromAddonJar(File file) { + try (JarFile jar = new JarFile(addon.getFile())) { + // Obtain any locale files, save them and update + Util.listJarFiles(jar, PHASES, ".yml").forEach(lf -> addon.saveResource(lf, file, false, true)); + } catch (Exception e) { + addon.logError(e.getMessage()); + } + } + + private void loadPhase(File phaseFile) throws IOException { + addon.log("Loading " + phaseFile.getName()); + // Load the config file + YamlConfiguration oneBlocks = new YamlConfiguration(); + try { + oneBlocks.load(phaseFile); + } catch (Exception e) { + addon.logError(e.getMessage()); + return; + } + for (String blockNumber : oneBlocks.getKeys(false)) { + Integer blockNum = Integer.valueOf(blockNumber); + OneBlockPhase obPhase = blockProbs.computeIfAbsent(blockNum, k -> new OneBlockPhase(blockNumber)); + // Get config Section + ConfigurationSection phase = oneBlocks.getConfigurationSection(blockNumber); + // goto + if (phase.contains(GOTO_BLOCK)) { + obPhase.setGotoBlock(phase.getInt(GOTO_BLOCK, 0)); + continue; + } + initBlock(blockNumber, obPhase, phase); + // Blocks + addBlocks(obPhase, phase); + // Mobs + addMobs(obPhase, phase); + // Chests + addChests(obPhase, phase); + // Commands + addCommands(obPhase, phase); + // Requirements + addRequirements(obPhase, phase); + // Add to the map + blockProbs.put(blockNum, obPhase); + } + } + + /** + * Load in the phase's init + * + * @param blockNumber string representation of this phase's block number + * @param obPhase OneBlockPhase + * @param phase configuration section being read + * @throws IOException if there's an error in the config file + */ + void initBlock(String blockNumber, OneBlockPhase obPhase, ConfigurationSection phase) throws IOException { + if (phase.contains(NAME, true)) { + if (obPhase.getPhaseName() != null) { + throw new IOException( + "Block " + blockNumber + ": Phase name trying to be set to " + phase.getString(NAME) + + " but already set to " + obPhase.getPhaseName() + ". Duplicate phase file?"); + } + // name + obPhase.setPhaseName(phase.getString(NAME, blockNumber)); + } + // biome + if (phase.contains(BIOME, true)) { + if (obPhase.getPhaseBiome() != null) { + throw new IOException("Block " + blockNumber + ": Biome trying to be set to " + phase.getString(BIOME) + + " but already set to " + obPhase.getPhaseBiome() + " Duplicate phase file?"); + } + obPhase.setPhaseBiome(getBiome(phase.getString(BIOME))); + } + // First block + if (phase.contains(FIRST_BLOCK)) { + if (obPhase.getFirstBlock() != null) { + throw new IOException( + "Block " + blockNumber + ": First block trying to be set to " + phase.getString(FIRST_BLOCK) + + " but already set to " + obPhase.getFirstBlock() + " Duplicate phase file?"); + } + addFirstBlock(obPhase, phase.getString(FIRST_BLOCK)); + } + // Icon block + if (phase.contains(ICON)) { + ItemStack icon = ItemParser.parse(phase.getString(ICON)); + + if (icon == null) { + throw new IOException("ItemParser failed to parse icon: '" + phase.getString(ICON) + "' for phase " + + obPhase.getFirstBlock() + ". Can you check if it is correct?"); + } + + obPhase.setIconBlock(icon); + } + // First blocks + if (phase.contains(FIXED_BLOCKS)) { + if (!obPhase.getFixedBlocks().isEmpty()) { + throw new IOException( + "Block " + blockNumber + ": Fixed blocks trying to be set to " + phase.getString(FIXED_BLOCKS) + + " but already set to " + obPhase.getFixedBlocks() + " Duplicate phase file?"); + } + addFixedBlocks(obPhase, phase.getConfigurationSection(FIXED_BLOCKS)); + } + + if (phase.contains(HOLOGRAMS)) { + if (!obPhase.getHologramLines().isEmpty()) { + throw new IOException( + "Block " + blockNumber + ": Hologram Lines trying to be set to " + phase.getString(HOLOGRAMS) + + " but already set to " + obPhase.getHologramLines() + " Duplicate phase file?"); + } + addHologramLines(obPhase, phase.getConfigurationSection(HOLOGRAMS)); + } + } + + private void addFixedBlocks(OneBlockPhase obPhase, ConfigurationSection fb) { + if (fb == null) { + return; + } + Map result = new HashMap<>(); + for (String key : fb.getKeys(false)) { + if (!NumberUtils.isNumber(key)) { + addon.logError("Fixed block key must be an integer. " + key); + continue; + } + int k = Integer.parseInt(key); + if (fb.isConfigurationSection(key)) { + Map map = fb.getConfigurationSection(key).getValues(false); + Optional customBlock = OneBlockCustomBlockCreator.create(map); + if (customBlock.isPresent()) { + result.put(k, new OneBlockObject(customBlock.get(), 0)); + } else { + addon.logError("Fixed block key " + key + " material is not a valid custom block. Ignoring."); + } + } else { + String mat = fb.getString(key); + if (mat == null) { + continue; + } + + Optional customBlock = OneBlockCustomBlockCreator.create(mat); + if (customBlock.isPresent()) { + result.put(k, new OneBlockObject(customBlock.get(), 0)); + } else { + Material m = Material.matchMaterial(mat); + if (m != null && m.isBlock()) { + result.put(k, new OneBlockObject(m, 0)); + } else { + addon.logError("Fixed block key " + key + " material is invalid or not a block. Ignoring."); + } + } + } + } + // Set the first block if it exists + if (result.containsKey(0)) { + obPhase.setFirstBlock(result.get(0)); + } + // Store the remainder + obPhase.setFixedBlocks(result); + + } + + private void addHologramLines(OneBlockPhase obPhase, ConfigurationSection fb) { + if (fb == null) + return; + Map result = new HashMap<>(); + for (String key : fb.getKeys(false)) { + if (!NumberUtils.isNumber(key)) { + addon.logError("Fixed block key must be an integer. " + key); + continue; + } + int k = Integer.parseInt(key); + String line = fb.getString(key); + if (line != null) { + result.put(k, line); + } + } + // Set Hologram Lines + obPhase.setHologramLines(result); + + } + + private Biome getBiome(String string) { + if (string == null) { + return Biome.PLAINS; + } + if (Enums.getIfPresent(Biome.class, string).isPresent()) { + return Biome.valueOf(string); + } + // Special case for nether + if (string.equals("NETHER") || string.equals("NETHER_WASTES")) { + return Enums.getIfPresent(Biome.class, "NETHER") + .or(Enums.getIfPresent(Biome.class, "NETHER_WASTES").or(Biome.PLAINS)); + } + addon.logError("Biome " + string.toUpperCase() + " is invalid! Use one of these..."); + addon.logError(Arrays.stream(Biome.values()).map(Biome::name).collect(Collectors.joining(","))); + return Biome.PLAINS; + } + + void addFirstBlock(OneBlockPhase obPhase, @Nullable String material) { + if (material == null) { + return; + } + Material m = Material.matchMaterial(material); + if (m == null || !m.isBlock()) { + addon.logError("Bad firstBlock material: " + material); + } else { + obPhase.setFirstBlock(new OneBlockObject(m, 0)); + } + } + + void addCommands(OneBlockPhase obPhase, ConfigurationSection phase) { + if (phase.contains(START_COMMANDS)) { + obPhase.setStartCommands(phase.getStringList(START_COMMANDS)); + } + if (phase.contains(END_COMMANDS)) { + obPhase.setEndCommands(phase.getStringList(END_COMMANDS)); + } + if (phase.contains(END_COMMANDS_FIRST_TIME)) { + obPhase.setFirstTimeEndCommands(phase.getStringList(END_COMMANDS_FIRST_TIME)); + } + } + + void addRequirements(OneBlockPhase obPhase, ConfigurationSection phase) { + List reqList = new ArrayList<>(); + if (!phase.isConfigurationSection(REQUIREMENTS)) { + return; + } + ConfigurationSection reqs = phase.getConfigurationSection(REQUIREMENTS); + for (ReqType key : Requirement.ReqType.values()) { + if (reqs.contains(key.getKey())) { + Requirement r; + if (key.getClazz().equals(Double.class)) { + r = new Requirement(key, reqs.getDouble(key.getKey())); + } else if (key.getClazz().equals(Long.class)) { + r = new Requirement(key, reqs.getLong(key.getKey())); + } else { + r = new Requirement(key, reqs.getString(key.getKey())); + } + reqList.add(r); + } + } + obPhase.setRequirements(reqList); + } + + void addChests(OneBlockPhase obPhase, ConfigurationSection phase) throws IOException { + if (!phase.isConfigurationSection(CHESTS)) { + return; + } + if (!obPhase.getChests().isEmpty()) { + throw new IOException(obPhase.getPhaseName() + ": Chests cannot be set more than once. Duplicate file?"); + } + ConfigurationSection chests = phase.getConfigurationSection(CHESTS); + for (String chestId : chests.getKeys(false)) { + ConfigurationSection chest = chests.getConfigurationSection(chestId); + Rarity rarity = Rarity.COMMON; + try { + rarity = OneBlockObject.Rarity.valueOf(chest.getString(RARITY, "COMMON").toUpperCase()); + } catch (Exception e) { + addon.logError( + "Rarity value of " + chest.getString(RARITY, "UNKNOWN") + " is invalid! Use one of these..."); + addon.logError(Arrays.stream(Rarity.values()).map(Rarity::name).collect(Collectors.joining(","))); + rarity = Rarity.COMMON; + } + Map items = new HashMap<>(); + ConfigurationSection contents = chest.getConfigurationSection(CONTENTS); + if (contents != null) { + for (String index : contents.getKeys(false)) { + int slot = Integer.parseInt(index); + ItemStack item = contents.getItemStack(index); + if (item != null) { + items.put(slot, item); + } + } + } + obPhase.addChest(items, rarity); + } + } + + void addMobs(OneBlockPhase obPhase, ConfigurationSection phase) throws IOException { + if (!phase.isConfigurationSection(MOBS)) { + return; + } + if (!obPhase.getMobs().isEmpty()) { + throw new IOException(obPhase.getPhaseName() + ": Mobs cannot be set more than once. Duplicate file?"); + } + ConfigurationSection mobs = phase.getConfigurationSection(MOBS); + for (String entity : mobs.getKeys(false)) { + String name = entity.toUpperCase(Locale.ENGLISH); + EntityType et = null; + // Pig zombie handling + if (name.equals("PIG_ZOMBIE") || name.equals("ZOMBIFIED_PIGLIN")) { + et = Enums.getIfPresent(EntityType.class, "ZOMBIFIED_PIGLIN") + .or(Enums.getIfPresent(EntityType.class, "PIG_ZOMBIE").or(EntityType.PIG)); + } else { + et = Enums.getIfPresent(EntityType.class, name).orNull(); + } + if (et == null) { + // Does not exist + addon.logError("Bad entity type in " + obPhase.getPhaseName() + ": " + entity); + addon.logError("Try one of these..."); + addon.logError(Arrays.stream(EntityType.values()).filter(EntityType::isSpawnable) + .filter(EntityType::isAlive).map(EntityType::name).collect(Collectors.joining(","))); + return; + } + if (et.isSpawnable() && et.isAlive()) { + if (mobs.getInt(entity) > 0) { + obPhase.addMob(et, mobs.getInt(entity)); + } else { + addon.logWarning("Bad entity weight for " + obPhase.getPhaseName() + ": " + entity + + ". Must be positive number above 1."); + } + } else { + addon.logError("Entity type is not spawnable " + obPhase.getPhaseName() + ": " + entity); + } + } + } + + void addBlocks(OneBlockPhase obPhase, ConfigurationSection phase) { + if (phase.isConfigurationSection(BLOCKS)) { + ConfigurationSection blocks = phase.getConfigurationSection(BLOCKS); + for (String material : blocks.getKeys(false)) { + if (Material.getMaterial(material) != null) { + addMaterial(obPhase, material, Objects.toString(blocks.get(material))); + } else { + if (addon.hasItemsAdder()) { + CustomBlock block = CustomBlock.getInstance(material); + if (block != null) { + addItemsAdderBlock(obPhase, material, Objects.toString(blocks.get(material))); + } else if (ItemsAdder.getAllItems() != null) { + if (ItemsAdder.getAllItems().size() != 0) { + addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material); + } + } + } else { + addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material); + } + } + } + } else if (phase.isList(BLOCKS)) { + List> blocks = phase.getMapList(BLOCKS); + for (Map map : blocks) { + if (map.size() == 1) { + Map.Entry entry = map.entrySet().iterator().next(); + if (addMaterial(obPhase, Objects.toString(entry.getKey()), Objects.toString(entry.getValue()))) { + continue; + } + } + + int probability = Integer.parseInt(Objects.toString(map.get("probability"), "0")); + Optional customBlock = OneBlockCustomBlockCreator.create(map); + if (customBlock.isPresent()) { + obPhase.addCustomBlock(customBlock.get(), probability); + } else { + addon.logError("Bad custom block in " + obPhase.getPhaseName() + ": " + map); + } + } + } + } + + private boolean addMaterial(OneBlockPhase obPhase, String material, String probability) { + int prob; + try { + prob = Integer.parseInt(probability); + } catch (Exception e) { + return false; + } + + if (prob < 1) { + addon.logWarning("Bad item weight for " + obPhase.getPhaseName() + ": " + material + + ". Must be positive number above 1."); + return false; + } + + // Register if the material is a valid custom block and can be created from the + // short creator from OneBlockCustomBlockCreator + Optional optionalCustomBlock = OneBlockCustomBlockCreator.create(material); + if (optionalCustomBlock.isPresent()) { + obPhase.addCustomBlock(optionalCustomBlock.get(), prob); + return true; + } + + // Otherwise, register the material as a block + Material m = Material.matchMaterial(material); + if (m == null || !m.isBlock()) { + addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material); + return false; + } + obPhase.addBlock(m, prob); + return true; + } + + private void addItemsAdderBlock(OneBlockPhase obPhase, String block, String probability) { + int prob; + try { + prob = Integer.parseInt(probability); + if (prob < 1) { + addon.logWarning("Bad item weight for " + obPhase.getPhaseName() + ": " + block + + ". Must be positive number above 1."); + } else { + obPhase.addItemsAdderCustomBlock(block, prob); + } + } catch (Exception e) { + addon.logError("Bad item weight for " + obPhase.getPhaseName() + ": " + block + ". Must be a number."); + } + + } + + /** + * Return the current phase for the block count + * + * @param blockCount - number of blocks mined + * @return the one block phase based on blockCount or null if there is none + */ + @Nullable + public OneBlockPhase getPhase(int blockCount) { + Entry en = blockProbs.floorEntry(blockCount); + return en != null ? en.getValue() : null; + } + + /** + * @return list of phase names with spaces replaced by underscore so they are + * one word + */ + public List getPhaseList() { + return blockProbs.values().stream().map(OneBlockPhase::getPhaseName).filter(Objects::nonNull) + .map(n -> n.replace(" ", "_")).collect(Collectors.toList()); + } + + /** + * @return the blockProbs + */ + public NavigableMap getBlockProbs() { + return blockProbs; + } + + /** + * Get phase by name. Name should have any spaces converted to underscores. Case + * insensitive. + * + * @param name - name to search + * @return optional OneBlockPhase + */ + public Optional getPhase(String name) { + return blockProbs.values().stream() + .filter(p -> p.getPhaseName() != null && (p.getPhaseName().equalsIgnoreCase(name) + || p.getPhaseName().replace(" ", "_").equalsIgnoreCase(name))) + .findFirst(); + } + + /** + * Save all the phases in memory to disk. + * + * @return true if saved + */ + + public boolean saveOneBlockConfig() { + // Go through each phase + boolean success = true; + for (OneBlockPhase p : blockProbs.values()) { + success = savePhase(p); + } + return success; + } + + /** + * Save a phase + * + * @param p OneBlockPhase + * @return true if successfully saved + */ + public boolean savePhase(OneBlockPhase p) { + if (!saveMainPhase(p)) { + // Failure + return false; + } + // No chests in goto phases + if (p.isGotoPhase()) { + // Done + return true; + } + return saveChestPhase(p); + } + + private boolean saveMainPhase(OneBlockPhase p) { + YamlConfiguration oneBlocks = new YamlConfiguration(); + ConfigurationSection phSec = oneBlocks.createSection(p.getBlockNumber()); + // Check for a goto block + if (p.isGotoPhase()) { + phSec.set(GOTO_BLOCK, p.getGotoBlock()); + } else { + phSec.set(NAME, p.getPhaseName()); + if (p.getIconBlock() != null) { + phSec.set(ICON, p.getIconBlock().getType().name()); + } + if (p.getFirstBlock() != null) { + phSec.set(FIRST_BLOCK, p.getFirstBlock().getMaterial().name()); + } + if (p.getPhaseBiome() != null) { + phSec.set(BIOME, p.getPhaseBiome().name()); + } + saveBlocks(phSec, p); + saveEntities(phSec, p); + saveHolos(phSec, p); + saveCommands(phSec, p); + } + try { + // Save + File phaseFile = new File(addon.getDataFolder() + File.separator + PHASES, getPhaseFileName(p) + ".yml"); + oneBlocks.save(phaseFile); + } catch (IOException e) { + addon.logError("Could not save phase " + p.getPhaseName() + " " + e.getMessage()); + return false; + } + return true; + } + + private void saveCommands(ConfigurationSection phSec, OneBlockPhase p) { + phSec.set(START_COMMANDS, p.getStartCommands()); + phSec.set(END_COMMANDS, p.getEndCommands()); + + } + + private void saveHolos(ConfigurationSection phSec, OneBlockPhase p) { + if (p.getHologramLines() == null) + return; + ConfigurationSection holos = phSec.createSection(HOLOGRAMS); + p.getHologramLines().forEach((k, v) -> holos.set(String.valueOf(k), v)); + } + + private boolean saveChestPhase(OneBlockPhase p) { + YamlConfiguration oneBlocks = new YamlConfiguration(); + ConfigurationSection phSec = oneBlocks.createSection(p.getBlockNumber()); + saveChests(phSec, p); + try { + // Save + File phaseFile = new File(addon.getDataFolder() + File.separator + PHASES, + getPhaseFileName(p) + "_chests.yml"); + oneBlocks.save(phaseFile); + } catch (IOException e) { + addon.logError("Could not save chest phase " + p.getPhaseName() + " " + e.getMessage()); + return false; + } + return true; + } + + private String getPhaseFileName(OneBlockPhase p) { + if (p.isGotoPhase()) { + return p.getBlockNumber() + "_goto_" + p.getGotoBlock(); + } + return p.getBlockNumber() + "_" + + (p.getPhaseName() == null ? "" : p.getPhaseName().toLowerCase().replace(' ', '_')); + } + + private void saveChests(ConfigurationSection phSec, OneBlockPhase phase) { + ConfigurationSection chests = phSec.createSection(CHESTS); + int index = 1; + for (OneBlockObject chest : phase.getChests()) { + ConfigurationSection c = chests.createSection(String.valueOf(index++)); + c.set(CONTENTS, chest.getChest()); + c.set(RARITY, chest.getRarity().name()); + } + + } + + private void saveEntities(ConfigurationSection phSec, OneBlockPhase phase) { + ConfigurationSection mobs = phSec.createSection(MOBS); + phase.getMobs().forEach((k, v) -> mobs.set(k.name(), v)); + } + + private void saveBlocks(ConfigurationSection phSec, OneBlockPhase phase) { + ConfigurationSection fixedBlocks = phSec.createSection(FIXED_BLOCKS); + phase.getFixedBlocks().forEach((k, v) -> fixedBlocks.set(String.valueOf(k), v.getMaterial().name())); + ConfigurationSection blocks = phSec.createSection(BLOCKS); + phase.getBlocks().forEach((k, v) -> blocks.set(k.name(), v)); + + } + + /** + * Get the phase after this one + * + * @param phase - one block phase + * @return next phase or null if there isn't one + */ + @SuppressWarnings("WrapperTypeMayBePrimitive") + @Nullable + public OneBlockPhase getNextPhase(@NonNull OneBlockPhase phase) { + // These are Integer objects because GSON can yield nulls if they do not exist + Integer blockNum = phase.getBlockNumberValue(); + Integer nextKey = blockProbs.ceilingKey(blockNum + 1); + return nextKey != null ? this.getPhase(nextKey) : null; + } + + /** + * Get the number of blocks until the next phase after this one + * + * @param obi - one block island + * @return number of blocks to the next phase. If there is no phase after -1 is + * returned. + */ + public int getNextPhaseBlocks(@NonNull OneBlockIslands obi) { + Integer blockNum = obi.getBlockNumber(); + Integer nextKey = blockProbs.ceilingKey(blockNum + 1); + if (nextKey == null) { + return -1; + } + OneBlockPhase nextPhase = this.getPhase(nextKey); + return nextPhase == null ? -1 : (nextPhase.getBlockNumberValue() - obi.getBlockNumber()); + } + + /** + * Get the number of blocks for this phase + * + * @param obi - one block island + * @return number of blocks for this current phase. If there is no phase after + * -1 is returned. + */ + public int getPhaseBlocks(@NonNull OneBlockIslands obi) { + Integer blockNum = obi.getBlockNumber(); + Integer nextKey = blockProbs.ceilingKey(blockNum + 1); + if (nextKey == null) { + return -1; + } + OneBlockPhase thisPhase = this.getPhase(blockNum); + if (thisPhase == null) + return -1; + OneBlockPhase nextPhase = this.getPhase(nextKey); + return nextPhase == null ? -1 : (nextPhase.getBlockNumberValue() - thisPhase.getBlockNumberValue()); + } + + /** + * Get the percentage done of this phase + * + * @param obi - one block island + * @return percentage done. If there is no next phase then return 0 + */ + public double getPercentageDone(@NonNull OneBlockIslands obi) { + int blockNum = obi.getBlockNumber(); + OneBlockPhase thisPhase = this.getPhase(blockNum); + if (thisPhase == null) { + return 0; + } + Integer nextKey = blockProbs.ceilingKey(blockNum + 1); + if (nextKey == null) { + return 0; + } + OneBlockPhase nextPhase = this.getPhase(nextKey); + if (nextPhase == null) { + return 0; + } + int phaseSize = nextPhase.getBlockNumberValue() - thisPhase.getBlockNumberValue(); + return 100 - (100 * (double) (nextPhase.getBlockNumberValue() - obi.getBlockNumber()) / phaseSize); + } + + public void getProbs(OneBlockPhase phase) { + // Find the phase after this one + Integer blockNum = Integer.valueOf(phase.getBlockNumber()); + Integer nextKey = blockProbs.ceilingKey(blockNum + 1); + if (nextKey != null) { + // This is the size of the phase in blocks + int phaseSize = nextKey - blockNum; + int blockTotal = phase.getBlockTotal(); + int likelyChestTotal = 0; + double totalBlocks = 0; + // Now calculate the relative block probability + for (Entry en : phase.getBlocks().entrySet()) { + double chance = (double) en.getValue() / blockTotal; + double likelyNumberGenerated = chance * phaseSize; + totalBlocks += likelyNumberGenerated; + String report = en.getKey() + " likely generated = " + Math.round(likelyNumberGenerated) + " = " + + Math.round(likelyNumberGenerated * 100 / phaseSize) + "%"; + if (likelyNumberGenerated < 1) { + addon.logWarning(report); + } else { + addon.log(report); + } + if (en.getKey().equals(Material.CHEST)) { + likelyChestTotal = (int) Math.round(likelyNumberGenerated); + } + } + addon.log("Total blocks generated = " + totalBlocks); + // Get the specific chest probability + if (likelyChestTotal == 0) { + addon.logWarning("No chests will be generated"); + return; + } + addon.log("**** A total of " + likelyChestTotal + " chests will be generated ****"); + // Now calculate chest chances + double lastChance = 0; + for (Entry en : OneBlockPhase.CHEST_CHANCES.entrySet()) { + // Get the number of chests in this rarity group + int num = phase.getChestsMap().getOrDefault(en.getValue(), Collections.emptyList()).size(); + double likelyNumberGenerated = (en.getKey() - lastChance) * likelyChestTotal; + lastChance = en.getKey(); + String report = num + " " + en.getValue() + " chests in phase. Likely number generated = " + + Math.round(likelyNumberGenerated); + if (num > 0 && likelyNumberGenerated < 1) { + addon.logWarning(report); + } else { + addon.log(report); + } + + } + // Mobs + addon.log("-=-=-=-= Mobs -=-=-=-=-"); + double totalMobs = 0; + // Now calculate the relative block probability + for (Entry en : phase.getMobs().entrySet()) { + double chance = (double) en.getValue() / phase.getTotal(); + double likelyNumberGenerated = chance * phaseSize; + totalMobs += likelyNumberGenerated; + String report = en.getKey() + " likely generated = " + Math.round(likelyNumberGenerated) + " = " + + Math.round(likelyNumberGenerated * 100 / phaseSize) + "%"; + if (likelyNumberGenerated < 1) { + addon.logWarning(report); + } else { + addon.log(report); + } + } + addon.log("**** A total of " + Math.round(totalMobs) + " mobs will likely be generated ****"); + } + } + + /** + * Get all the probs for each phases and log to console + */ + public void getAllProbs() { + blockProbs.values().forEach(this::getProbs); + } + + /** + * Get the next phase name + * + * @param obi - one block island + * @return next phase name or an empty string + */ + public String getNextPhase(@NonNull OneBlockIslands obi) { + return getPhase(obi.getPhaseName()).map(this::getNextPhase) // Next phase or null + .filter(Objects::nonNull).map(OneBlockPhase::getPhaseName).orElse(""); + } } diff --git a/src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest.java b/src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest.java deleted file mode 100644 index 9be1c3e1..00000000 --- a/src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest.java +++ /dev/null @@ -1,296 +0,0 @@ -package world.bentobox.aoneblock.oneblocks; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.UnsafeValues; -import org.bukkit.block.Biome; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.inventory.meta.ItemMeta; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.reflect.Whitebox; - -import world.bentobox.aoneblock.AOneBlock; -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.addons.AddonDescription; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.managers.AddonsManager; - -@SuppressWarnings("deprecation") -@RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, BentoBox.class, User.class}) -public class OneBlocksManagerTest { - private static File jFile; - private static YamlConfiguration oneBlocks; - @Mock - private BentoBox plugin; - private AOneBlock addon; - @Mock - private AddonsManager am; - private OneBlocksManager obm; - - @BeforeClass - public static void beforeClass() throws IOException, InvalidConfigurationException { - // Make the addon jar - jFile = new File("addon.jar"); - // Copy over config file from src folder - /* - Path fromPath = Paths.get("src/main/resources/config.yml"); - Path path = Paths.get("config.yml"); - Files.copy(fromPath, path);*/ - // Dummy oneblocks.yml - String oneblocks = - "'0':\n" + - " name: Plains\n" + - " firstBlock: GRASS_BLOCK\n" + - " biome: PLAINS\n" + - " blocks:\n" + - " GRASS_BLOCK: 2000\n" + - " BIRCH_LOG: 500\n" + - " mobs:\n" + - " SHEEP: 150\n" + - " VILLAGER: 30\n" + - "'700':\n" + - " name: Underground\n" + - " firstBlock: STONE\n" + - " biome: TAIGA\n" + - " blocks:\n" + - " EMERALD_ORE: 5\n" + - " COBWEB: 250\n" + - " DIRT: 500\n" + - "'11000':\n" + - " gotoBlock: 0"; - oneBlocks = new YamlConfiguration(); - oneBlocks.loadFromString(oneblocks); - // Save - File obFileDir = new File("phases"); - - File obFile = new File(obFileDir, "0_plains.yml"); - obFileDir.mkdirs(); - oneBlocks.save(obFile); - /* - // Copy over block config file from src folder - fromPath = Paths.get("src/main/resources/oneblocks.yml"); - path = Paths.get("oneblocks.yml"); - Files.copy(fromPath, path); - */ - try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { - //Added the new files to the jar. - try (FileInputStream fis = new FileInputStream(obFile)) { - byte[] buffer = new byte[1024]; - int bytesRead = 0; - JarEntry entry = new JarEntry(obFile.toPath().toString()); - tempJarOutputStream.putNextEntry(entry); - while((bytesRead = fis.read(buffer)) != -1) { - tempJarOutputStream.write(buffer, 0, bytesRead); - } - } - } - } - - /** - * @throws java.lang.Exception - */ - @Before - public void setUp() throws Exception { - // Set up plugin - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - // Addon - addon = new AOneBlock(); - File dataFolder = new File("addons/AOneBlock"); - addon.setDataFolder(dataFolder); - addon.setFile(jFile); - AddonDescription desc = new AddonDescription.Builder("bentobox", "AOneBlock", "1.3").description("test").authors("tastybento").build(); - addon.setDescription(desc); - //addon.setSettings(new Settings()); - // Addons manager - when(plugin.getAddonsManager()).thenReturn(am); - - // Bukkit - PowerMockito.mockStatic(Bukkit.class); - ItemMeta meta = mock(ItemMeta.class); - ItemFactory itemFactory = mock(ItemFactory.class); - when(itemFactory.getItemMeta(any())).thenReturn(meta); - when(Bukkit.getItemFactory()).thenReturn(itemFactory); - UnsafeValues unsafe = mock(UnsafeValues.class); - when(unsafe.getDataVersion()).thenReturn(777); - when(Bukkit.getUnsafe()).thenReturn(unsafe); - - // Class under test - obm = new OneBlocksManager(addon); - } - - /** - * @throws java.lang.Exception - */ - @After - public void tearDown() throws Exception { - deleteAll(new File("database")); - } - - @AfterClass - public static void cleanUp() throws Exception { - - new File("addon.jar").delete(); - new File("config.yml").delete(); - - deleteAll(new File("addons")); - deleteAll(new File("phases")); - } - - private static void deleteAll(File file) throws IOException { - if (file.exists()) { - Files.walk(file.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - } - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#OneBlocksManager(world.bentobox.aoneblock.AOneBlock)}. - * @throws IOException - */ - @Test - public void testOneBlocksManager() throws IOException { - File f = new File("phases", "0_plains.yml"); - assertTrue(f.exists()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#loadPhases()}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - //@Ignore("Cannot deserialize objects right now") - @Test - public void testLoadPhases() throws NumberFormatException, IOException, InvalidConfigurationException { - obm.loadPhases(); - verify(plugin, never()).logError(anyString()); - assertEquals(Material.GRASS_BLOCK, obm.getPhase(0).getFirstBlock().getMaterial()); - assertEquals(Biome.PLAINS, obm.getPhase(0).getPhaseBiome()); - assertEquals("Plains", obm.getPhase(0).getPhaseName()); - assertNull(obm.getPhase(0).getGotoBlock()); - - assertEquals(Material.STONE, obm.getPhase(700).getFirstBlock().getMaterial()); - assertEquals(Biome.TAIGA, obm.getPhase(700).getPhaseBiome()); - assertEquals("Underground", obm.getPhase(700).getPhaseName()); - assertNull(obm.getPhase(700).getGotoBlock()); - - assertEquals(0, (int)obm.getPhase(11000).getGotoBlock()); - - - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPhaseList()}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - @Test - public void testGetPhaseList() throws NumberFormatException, IOException, InvalidConfigurationException { - testLoadPhases(); - List l = obm.getPhaseList(); - assertEquals(2, l.size()); - assertEquals("Plains", l.get(0)); - assertEquals("Underground", l.get(1)); - - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPhase(java.lang.String)}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - @Test - public void testGetPhaseString() throws NumberFormatException, IOException, InvalidConfigurationException { - testLoadPhases(); - assertFalse(obm.getPhase("sdf").isPresent()); - assertTrue(obm.getPhase("Plains").isPresent()); - assertTrue(obm.getPhase("Underground").isPresent()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#saveOneBlockConfig()}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - @Test - public void testSaveOneBlockConfig() throws NumberFormatException, IOException, InvalidConfigurationException { - //testLoadPhases(); - //assertTrue(obm.saveOneBlockConfig()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getNextPhase(OneBlockPhase)}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - @Test - public void testGetNextPhase() throws NumberFormatException, IOException, InvalidConfigurationException { - testLoadPhases(); - OneBlockPhase plains = obm.getPhase("Plains").get(); - OneBlockPhase underground = obm.getPhase("Underground").get(); - OneBlockPhase gotoPhase = obm.getPhase(11000); - assertEquals(underground, obm.getNextPhase(plains)); - assertEquals(gotoPhase, obm.getNextPhase(underground)); - assertNull(obm.getNextPhase(gotoPhase)); - } - - @Ignore - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getProbs(world.bentobox.aoneblock.oneblocks.OneBlockPhase)}. - */ - @Test - public void testGetProbs() { - - } - - @Ignore - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getAllProbs()}. - */ - @Test - public void testGetAllProbs() { - fail("Not yet implemented"); // TODO - } - -} diff --git a/src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest2.java b/src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest2.java deleted file mode 100644 index b894ef1a..00000000 --- a/src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest2.java +++ /dev/null @@ -1,446 +0,0 @@ -package world.bentobox.aoneblock.oneblocks; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; -import java.util.List; -import java.util.NavigableMap; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.UnsafeValues; -import org.bukkit.block.Biome; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.inventory.meta.ItemMeta; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.reflect.Whitebox; - -import world.bentobox.aoneblock.AOneBlock; -import world.bentobox.aoneblock.dataobjects.OneBlockIslands; -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.addons.AddonDescription; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.managers.AddonsManager; - -/** - * @author tastybento - * - */ -@RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, BentoBox.class, User.class}) -public class OneBlocksManagerTest2 { - - private static File jFile; - private static YamlConfiguration oneBlocks; - @Mock - private BentoBox plugin; - private AOneBlock addon; - @Mock - private AddonsManager am; - private OneBlocksManager obm; - private OneBlockPhase obPhase; - @Mock - private @NonNull OneBlockIslands obi; - - @BeforeClass - public static void beforeClass() throws IOException, InvalidConfigurationException { - // Make the addon jar - jFile = new File("addon.jar"); - // Copy over config file from src folder - /* - Path fromPath = Paths.get("src/main/resources/config.yml"); - Path path = Paths.get("config.yml"); - Files.copy(fromPath, path);*/ - // Dummy oneblocks.yml - String oneblocks = - "'0':\n" + - " name: Plains\n" + - " icon: GRASS_BLOCK\n" + - " firstBlock: GRASS_BLOCK\n" + - " biome: PLAINS\n" + - " fixedBlocks:\n" + - " 0: GRASS_BLOCK\n" + - " 1: GRASS_BLOCK\n" + - " holograms:\n" + - " 0: &aGood Luck!\n" + - " blocks:\n" + - " GRASS_BLOCK: 2000\n" + - " BIRCH_LOG: 500\n" + - " mobs:\n" + - " SHEEP: 150\n" + - " VILLAGER: 30\n" + - "'700':\n" + - " name: Underground\n" + - " firstBlock: STONE\n" + - " biome: TAIGA\n" + - " blocks:\n" + - " EMERALD_ORE: 5\n" + - " COBWEB: 250\n" + - " DIRT: 500\n" + - "'11000':\n" + - " gotoBlock: 0"; - oneBlocks = new YamlConfiguration(); - oneBlocks.loadFromString(oneblocks); - // Save - File obFileDir = new File("phases"); - - File obFile = new File(obFileDir, "0_plains.yml"); - obFileDir.mkdirs(); - oneBlocks.save(obFile); - /* - // Copy over block config file from src folder - fromPath = Paths.get("src/main/resources/oneblocks.yml"); - path = Paths.get("oneblocks.yml"); - Files.copy(fromPath, path); - */ - try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { - //Added the new files to the jar. - try (FileInputStream fis = new FileInputStream(obFile)) { - byte[] buffer = new byte[1024]; - int bytesRead = 0; - JarEntry entry = new JarEntry(obFile.toPath().toString()); - tempJarOutputStream.putNextEntry(entry); - while((bytesRead = fis.read(buffer)) != -1) { - tempJarOutputStream.write(buffer, 0, bytesRead); - } - } - } - } - - /** - * @throws java.lang.Exception - */ - @Before - public void setUp() throws Exception { - // Set up plugin - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - // Addon - addon = new AOneBlock(); - File dataFolder = new File("addons/AOneBlock"); - addon.setDataFolder(dataFolder); - addon.setFile(jFile); - AddonDescription desc = new AddonDescription.Builder("bentobox", "AOneBlock", "1.3").description("test").authors("tastybento").build(); - addon.setDescription(desc); - //addon.setSettings(new Settings()); - // Addons manager - when(plugin.getAddonsManager()).thenReturn(am); - - // Bukkit - PowerMockito.mockStatic(Bukkit.class); - ItemMeta meta = mock(ItemMeta.class); - ItemFactory itemFactory = mock(ItemFactory.class); - when(itemFactory.getItemMeta(any())).thenReturn(meta); - when(Bukkit.getItemFactory()).thenReturn(itemFactory); - UnsafeValues unsafe = mock(UnsafeValues.class); - when(unsafe.getDataVersion()).thenReturn(777); - when(Bukkit.getUnsafe()).thenReturn(unsafe); - - // Phase - obPhase = new OneBlockPhase("0"); - - // Class under test - obm = new OneBlocksManager(addon); - } - - /** - * @throws java.lang.Exception - */ - @After - public void tearDown() throws Exception { - deleteAll(new File("database")); - } - - @AfterClass - public static void cleanUp() throws Exception { - - new File("addon.jar").delete(); - new File("config.yml").delete(); - - deleteAll(new File("addons")); - deleteAll(new File("phases")); - } - - private static void deleteAll(File file) throws IOException { - if (file.exists()) { - Files.walk(file.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - } - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#OneBlocksManager(world.bentobox.aoneblock.AOneBlock)}. - * @throws IOException - */ - @Test - public void testOneBlocksManager() throws IOException { - File f = new File("phases", "0_plains.yml"); - assertTrue(f.exists()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#loadPhases()}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - //@Ignore("Cannot deserialize objects right now") - @Test - public void testLoadPhases() throws NumberFormatException, IOException, InvalidConfigurationException { - obm.loadPhases(); - verify(plugin, never()).logError(anyString()); - assertEquals(Material.GRASS_BLOCK, obm.getPhase(0).getFirstBlock().getMaterial()); - assertEquals(Biome.PLAINS, obm.getPhase(0).getPhaseBiome()); - assertEquals("Plains", obm.getPhase(0).getPhaseName()); - assertNull(obm.getPhase(0).getGotoBlock()); - - assertEquals(Material.STONE, obm.getPhase(700).getFirstBlock().getMaterial()); - assertEquals(Biome.TAIGA, obm.getPhase(700).getPhaseBiome()); - assertEquals("Underground", obm.getPhase(700).getPhaseName()); - assertNull(obm.getPhase(700).getGotoBlock()); - - assertEquals(0, (int)obm.getPhase(11000).getGotoBlock()); - - - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPhaseList()}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - @Test - public void testGetPhaseList() throws NumberFormatException, IOException, InvalidConfigurationException { - testLoadPhases(); - List l = obm.getPhaseList(); - assertEquals(2, l.size()); - assertEquals("Plains", l.get(0)); - assertEquals("Underground", l.get(1)); - - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPhase(java.lang.String)}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - @Test - public void testGetPhaseString() throws NumberFormatException, IOException, InvalidConfigurationException { - testLoadPhases(); - assertFalse(obm.getPhase("sdf").isPresent()); - assertTrue(obm.getPhase("Plains").isPresent()); - assertTrue(obm.getPhase("Underground").isPresent()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#saveOneBlockConfig()}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - @Ignore("Not saving") - @Test - public void testSaveOneBlockConfig() throws NumberFormatException, IOException, InvalidConfigurationException { - //testLoadPhases(); - //assertTrue(obm.saveOneBlockConfig()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getNextPhase(OneBlockPhase)}. - * @throws InvalidConfigurationException - * @throws IOException - * @throws NumberFormatException - */ - @Test - public void testGetNextPhase() throws NumberFormatException, IOException, InvalidConfigurationException { - testLoadPhases(); - OneBlockPhase plains = obm.getPhase("Plains").get(); - OneBlockPhase underground = obm.getPhase("Underground").get(); - OneBlockPhase gotoPhase = obm.getPhase(11000); - assertEquals(underground, obm.getNextPhase(plains)); - assertEquals(gotoPhase, obm.getNextPhase(underground)); - assertNull(obm.getNextPhase(gotoPhase)); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#copyPhasesFromAddonJar(java.io.File)}. - * @throws IOException - */ - @Test - public void testCopyPhasesFromAddonJar() throws IOException { - File dest = new File("dest"); - dest.mkdir(); - obm.copyPhasesFromAddonJar(dest); - File check = new File(dest, "0_plains.yml"); - assertTrue(check.exists()); - // Clean up - deleteAll(dest); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#initBlock(java.lang.String, world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. - * @throws IOException - */ - @Test - public void testInitBlock() throws IOException { - System.out.println(oneBlocks); - obm.initBlock("0", obPhase, oneBlocks); - assertEquals("", obPhase.getPhaseName()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addFirstBlock(world.bentobox.aoneblock.oneblocks.OneBlockPhase, java.lang.String)}. - */ - @Test - public void testAddFirstBlock() { - obm.addFirstBlock(obPhase, "SPONGE"); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addCommands(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. - */ - @Test - public void testAddCommands() { - obm.addCommands(obPhase, oneBlocks); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addRequirements(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. - */ - @Test - public void testAddRequirements() { - obm.addRequirements(obPhase, oneBlocks); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addChests(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. - */ - @Test - public void testAddChests() throws IOException { - obm.addChests(obPhase, oneBlocks); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addMobs(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. - */ - @Test - public void testAddMobs() throws IOException { - obm.addMobs(obPhase, oneBlocks); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addBlocks(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. - */ - @Test - public void testAddBlocks() throws IOException { - obm.addBlocks(obPhase, oneBlocks); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPhase(int)}. - */ - @Test - public void testGetPhaseInt() { - @Nullable - OneBlockPhase phase = obm.getPhase(1); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getBlockProbs()}. - */ - @Test - public void testGetBlockProbs() { - NavigableMap probs = obm.getBlockProbs(); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#savePhase(world.bentobox.aoneblock.oneblocks.OneBlockPhase)}. - */ - @Test - public void testSavePhase() { - boolean result = obm.savePhase(obPhase); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getNextPhase(world.bentobox.aoneblock.oneblocks.OneBlockPhase)}. - */ - @Test - public void testGetNextPhaseOneBlockPhase() { - @Nullable - OneBlockPhase phase = obm.getNextPhase(obPhase); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getNextPhaseBlocks(world.bentobox.aoneblock.dataobjects.OneBlockIslands)}. - */ - @Test - public void testGetNextPhaseBlocks() { - int phase = obm.getNextPhaseBlocks(obi); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPercentageDone(world.bentobox.aoneblock.dataobjects.OneBlockIslands)}. - */ - @Test - public void testGetPercentageDone() { - double percent = obm.getPercentageDone(obi); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getProbs(world.bentobox.aoneblock.oneblocks.OneBlockPhase)}. - */ - @Test - public void testGetProbs() { - obm.getProbs(obPhase); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getAllProbs()}. - */ - @Test - public void testGetAllProbs() { - obm.getAllProbs(); - } - - /** - * Test method for {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getNextPhase(world.bentobox.aoneblock.dataobjects.OneBlockIslands)}. - */ - @Test - public void testGetNextPhaseOneBlockIslands() { - String phase = obm.getNextPhase(obi); - } - -} diff --git a/src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest3.java b/src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest3.java new file mode 100644 index 00000000..57323b39 --- /dev/null +++ b/src/test/java/world/bentobox/aoneblock/oneblocks/OneBlocksManagerTest3.java @@ -0,0 +1,482 @@ +package world.bentobox.aoneblock.oneblocks; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.NavigableMap; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.UnsafeValues; +import org.bukkit.block.Biome; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.meta.ItemMeta; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import world.bentobox.aoneblock.AOneBlock; +import world.bentobox.aoneblock.dataobjects.OneBlockIslands; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.AddonDescription; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.managers.AddonsManager; + +/** + * @author tastybento + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class }) +public class OneBlocksManagerTest3 { + + private static File jFile; + private static YamlConfiguration oneBlocks; + @Mock + private BentoBox plugin; + private AOneBlock addon; + @Mock + private AddonsManager am; + private OneBlocksManager obm; + private OneBlockPhase obPhase; + @Mock + private @NonNull OneBlockIslands obi; + + @BeforeClass + public static void beforeClass() throws IOException, InvalidConfigurationException { + // Make the addon jar + jFile = new File("addon.jar"); + // Copy over config file from src folder + /* + * Path fromPath = Paths.get("src/main/resources/config.yml"); Path path = + * Paths.get("config.yml"); Files.copy(fromPath, path); + */ + // Dummy oneblocks.yml + String oneblocks = "'0':\n" + " name: Plains\n" + " icon: GRASS_BLOCK\n" + " firstBlock: GRASS_BLOCK\n" + + " biome: PLAINS\n" + " fixedBlocks:\n" + " 0: GRASS_BLOCK\n" + " 1: GRASS_BLOCK\n" + + " holograms:\n" + " 0: &aGood Luck!\n" + " blocks:\n" + " GRASS_BLOCK: 2000\n" + + " BIRCH_LOG: 500\n" + " mobs:\n" + " SHEEP: 150\n" + " VILLAGER: 30\n" + "'700':\n" + + " name: Underground\n" + " firstBlock: STONE\n" + " biome: TAIGA\n" + " blocks:\n" + + " EMERALD_ORE: 5\n" + " COBWEB: 250\n" + " DIRT: 500\n" + "'11000':\n" + " gotoBlock: 0"; + oneBlocks = new YamlConfiguration(); + oneBlocks.loadFromString(oneblocks); + // Save + File obFileDir = new File("phases"); + + File obFile = new File(obFileDir, "0_plains.yml"); + obFileDir.mkdirs(); + oneBlocks.save(obFile); + /* + * // Copy over block config file from src folder fromPath = + * Paths.get("src/main/resources/oneblocks.yml"); path = + * Paths.get("oneblocks.yml"); Files.copy(fromPath, path); + */ + try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { + // Added the new files to the jar. + try (FileInputStream fis = new FileInputStream(obFile)) { + byte[] buffer = new byte[1024]; + int bytesRead = 0; + JarEntry entry = new JarEntry(obFile.toPath().toString()); + tempJarOutputStream.putNextEntry(entry); + while ((bytesRead = fis.read(buffer)) != -1) { + tempJarOutputStream.write(buffer, 0, bytesRead); + } + } + } + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + // Addon + addon = new AOneBlock(); + File dataFolder = new File("addons/AOneBlock"); + addon.setDataFolder(dataFolder); + addon.setFile(jFile); + AddonDescription desc = new AddonDescription.Builder("bentobox", "AOneBlock", "1.3").description("test") + .authors("tastybento").build(); + addon.setDescription(desc); + // addon.setSettings(new Settings()); + // Addons manager + when(plugin.getAddonsManager()).thenReturn(am); + + // Bukkit + PowerMockito.mockStatic(Bukkit.class); + ItemMeta meta = mock(ItemMeta.class); + ItemFactory itemFactory = mock(ItemFactory.class); + when(itemFactory.getItemMeta(any())).thenReturn(meta); + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + UnsafeValues unsafe = mock(UnsafeValues.class); + when(unsafe.getDataVersion()).thenReturn(777); + when(Bukkit.getUnsafe()).thenReturn(unsafe); + + // Phase + obPhase = new OneBlockPhase("0"); + + // Class under test + obm = new OneBlocksManager(addon); + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + deleteAll(new File("database")); + } + + @AfterClass + public static void cleanUp() throws Exception { + + new File("addon.jar").delete(); + new File("config.yml").delete(); + + deleteAll(new File("addons")); + deleteAll(new File("phases")); + } + + private static void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#OneBlocksManager(world.bentobox.aoneblock.AOneBlock)}. + * + * @throws IOException + */ + @Test + public void testOneBlocksManager() throws IOException { + File f = new File("phases", "0_plains.yml"); + assertTrue(f.exists()); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#loadPhases()}. + * + * @throws InvalidConfigurationException + * @throws IOException + * @throws NumberFormatException + */ + // @Ignore("Cannot deserialize objects right now") + @Test + public void testLoadPhases() throws NumberFormatException, IOException, InvalidConfigurationException { + obm.loadPhases(); + verify(plugin, never()).logError(anyString()); + assertEquals(Material.GRASS_BLOCK, obm.getPhase(0).getFirstBlock().getMaterial()); + assertEquals(Biome.PLAINS, obm.getPhase(0).getPhaseBiome()); + assertEquals("Plains", obm.getPhase(0).getPhaseName()); + assertNull(obm.getPhase(0).getGotoBlock()); + + assertEquals(Material.STONE, obm.getPhase(700).getFirstBlock().getMaterial()); + assertEquals(Biome.TAIGA, obm.getPhase(700).getPhaseBiome()); + assertEquals("Underground", obm.getPhase(700).getPhaseName()); + assertNull(obm.getPhase(700).getGotoBlock()); + + assertEquals(0, (int) obm.getPhase(11000).getGotoBlock()); + + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPhaseList()}. + * + * @throws InvalidConfigurationException + * @throws IOException + * @throws NumberFormatException + */ + @Test + public void testGetPhaseList() throws NumberFormatException, IOException, InvalidConfigurationException { + testLoadPhases(); + List l = obm.getPhaseList(); + assertEquals(2, l.size()); + assertEquals("Plains", l.get(0)); + assertEquals("Underground", l.get(1)); + + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPhase(java.lang.String)}. + * + * @throws InvalidConfigurationException + * @throws IOException + * @throws NumberFormatException + */ + @Test + public void testGetPhaseString() throws NumberFormatException, IOException, InvalidConfigurationException { + testLoadPhases(); + assertFalse(obm.getPhase("sdf").isPresent()); + assertTrue(obm.getPhase("Plains").isPresent()); + assertTrue(obm.getPhase("Underground").isPresent()); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#saveOneBlockConfig()}. + * + * @throws InvalidConfigurationException + * @throws IOException + * @throws NumberFormatException + */ + @Ignore("Not saving") + @Test + public void testSaveOneBlockConfig() throws NumberFormatException, IOException, InvalidConfigurationException { + // testLoadPhases(); + // assertTrue(obm.saveOneBlockConfig()); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getNextPhase(OneBlockPhase)}. + * + * @throws InvalidConfigurationException + * @throws IOException + * @throws NumberFormatException + */ + @Test + public void testGetNextPhase() throws NumberFormatException, IOException, InvalidConfigurationException { + testLoadPhases(); + OneBlockPhase plains = obm.getPhase("Plains").get(); + OneBlockPhase underground = obm.getPhase("Underground").get(); + OneBlockPhase gotoPhase = obm.getPhase(11000); + assertEquals(underground, obm.getNextPhase(plains)); + assertEquals(gotoPhase, obm.getNextPhase(underground)); + assertNull(obm.getNextPhase(gotoPhase)); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#copyPhasesFromAddonJar(java.io.File)}. + * + * @throws IOException + */ + @Test + public void testCopyPhasesFromAddonJar() throws IOException { + File dest = new File("dest"); + dest.mkdir(); + obm.copyPhasesFromAddonJar(dest); + File check = new File(dest, "0_plains.yml"); + assertTrue(check.exists()); + // Clean up + deleteAll(dest); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#initBlock(java.lang.String, world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. + * + * @throws IOException + */ + @Test + @Ignore + public void testInitBlock() throws IOException { + System.out.println(oneBlocks); + obm.initBlock("0", obPhase, oneBlocks); + assertEquals("", obPhase.getPhaseName()); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addFirstBlock(world.bentobox.aoneblock.oneblocks.OneBlockPhase, java.lang.String)}. + */ + @Test + public void testAddFirstBlockBadMateril() { + obm.addFirstBlock(obPhase, "shshhs"); + verify(plugin).logError("[AOneBlock] Bad firstBlock material: shshhs"); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addFirstBlock(world.bentobox.aoneblock.oneblocks.OneBlockPhase, java.lang.String)}. + */ + @Test + public void testAddFirstBlockNullMaterial() { + obm.addFirstBlock(obPhase, null); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addFirstBlock(world.bentobox.aoneblock.oneblocks.OneBlockPhase, java.lang.String)}. + */ + @Test + public void testAddFirstBlock() { + obm.addFirstBlock(obPhase, "SPONGE"); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addCommands(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. + */ + @Test + public void testAddCommands() { + obm.addCommands(obPhase, oneBlocks); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addRequirements(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. + */ + @Test + public void testAddRequirements() { + obm.addRequirements(obPhase, oneBlocks); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addChests(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. + */ + @Test + public void testAddChests() throws IOException { + obm.addChests(obPhase, oneBlocks); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addMobs(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. + */ + @Test + public void testAddMobs() throws IOException { + obm.addMobs(obPhase, oneBlocks); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#addBlocks(world.bentobox.aoneblock.oneblocks.OneBlockPhase, org.bukkit.configuration.ConfigurationSection)}. + */ + @Test + public void testAddBlocks() throws IOException { + obm.addBlocks(obPhase, oneBlocks); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPhase(int)}. + */ + @Test + public void testGetPhaseInt() { + @Nullable + OneBlockPhase phase = obm.getPhase(1); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getBlockProbs()}. + */ + @Test + public void testGetBlockProbs() { + NavigableMap probs = obm.getBlockProbs(); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#savePhase(world.bentobox.aoneblock.oneblocks.OneBlockPhase)}. + */ + @Test + public void testSavePhase() { + boolean result = obm.savePhase(obPhase); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getNextPhase(world.bentobox.aoneblock.oneblocks.OneBlockPhase)}. + */ + @Test + public void testGetNextPhaseOneBlockPhase() { + @Nullable + OneBlockPhase phase = obm.getNextPhase(obPhase); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getNextPhaseBlocks(world.bentobox.aoneblock.dataobjects.OneBlockIslands)}. + */ + @Test + public void testGetNextPhaseBlocks() { + int phase = obm.getNextPhaseBlocks(obi); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPercentageDone(world.bentobox.aoneblock.dataobjects.OneBlockIslands)}. + */ + @Test + public void testGetPercentageDone() { + double percent = obm.getPercentageDone(obi); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getProbs(world.bentobox.aoneblock.oneblocks.OneBlockPhase)}. + */ + @Test + public void testGetProbs() { + obm.getProbs(obPhase); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getAllProbs()}. + */ + @Test + public void testGetAllProbs() { + obm.getAllProbs(); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getNextPhase(world.bentobox.aoneblock.dataobjects.OneBlockIslands)}. + */ + @Test + public void testGetNextPhaseOneBlockIslands() { + String phase = obm.getNextPhase(obi); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.oneblocks.OneBlocksManager#getPhaseBlocks(world.bentobox.aoneblock.dataobjects.OneBlockIslands)}. + */ + @Test + public void testGetPhaseBlocks() { + assertEquals(-1, obm.getPhaseBlocks(obi)); + } + +} From 34783af9095e0fc52d0a28bda9e875861df032ac Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 10 Nov 2023 08:03:30 -0800 Subject: [PATCH 23/42] Version up 1.15.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6154cbe6..d031d341 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ -LOCAL - 1.14.2 + 1.15.0 BentoBoxWorld_AOneBlock bentobox-world From 5288546392ff9682c43b3c36bde7483425d06052 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 12 Nov 2023 14:03:22 -0800 Subject: [PATCH 24/42] BentoBox API 2.0.0 and RanksManager database test fix --- pom.xml | 2 +- .../bentobox/aoneblock/AOneBlockTest.java | 550 +++++++++--------- .../island/IslandSetCountCommandTest.java | 306 +++++----- 3 files changed, 464 insertions(+), 394 deletions(-) diff --git a/pom.xml b/pom.xml index d031d341..e52dca4a 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 2.0.9 1.20.1-R0.1-SNAPSHOT - 1.24.0-SNAPSHOT + 2.0.0-SNAPSHOT 2.6.2 1.3.0 diff --git a/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java b/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java index 3f77cab5..97f43fa7 100644 --- a/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java +++ b/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java @@ -12,17 +12,20 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.beans.IntrospectionException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.Comparator; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.logging.Logger; @@ -34,6 +37,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -51,6 +55,8 @@ import world.bentobox.bentobox.api.addons.AddonDescription; import world.bentobox.bentobox.api.configuration.Config; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.AbstractDatabaseHandler; +import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.managers.AddonsManager; @@ -59,274 +65,294 @@ import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.managers.PlaceholdersManager; +import world.bentobox.bentobox.managers.RanksManager; /** * @author tastybento * */ @RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, BentoBox.class, User.class, Config.class }) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Config.class, DatabaseSetup.class }) public class AOneBlockTest { - @Mock - private User user; - @Mock - private IslandsManager im; - @Mock - private Island island; - - private AOneBlock addon; - @Mock - private BentoBox plugin; - @Mock - private FlagsManager fm; - @Mock - private Settings settings; - @Mock - private PlaceholdersManager phm; - - /** - * @throws java.lang.Exception - */ - @Before - public void setUp() throws Exception { - // Set up plugin - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); - - // The database type has to be created one line before the thenReturn() to work! - DatabaseType value = DatabaseType.JSON; - when(plugin.getSettings()).thenReturn(settings); - when(settings.getDatabaseType()).thenReturn(value); - when(plugin.getPlaceholdersManager()).thenReturn(phm); - // Placeholders - when(phm.replacePlaceholders(any(), anyString())).thenAnswer(a -> (String)a.getArgument(1, String.class)); - - - // Command manager - CommandsManager cm = mock(CommandsManager.class); - when(plugin.getCommandsManager()).thenReturn(cm); - - // Player - Player p = mock(Player.class); - // Sometimes use Mockito.withSettings().verboseLogging() - when(user.isOp()).thenReturn(false); - UUID uuid = UUID.randomUUID(); - when(user.getUniqueId()).thenReturn(uuid); - when(user.getPlayer()).thenReturn(p); - when(user.getName()).thenReturn("tastybento"); - User.setPlugin(plugin); - - // Island World Manager - IslandWorldManager iwm = mock(IslandWorldManager.class); - when(plugin.getIWM()).thenReturn(iwm); - - - // Player has island to begin with - island = mock(Island.class); - when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island); - when(plugin.getIslands()).thenReturn(im); - - // Locales - // Return the reference (USE THIS IN THE FUTURE) - when(user.getTranslation(Mockito.anyString())).thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); - - // Server - PowerMockito.mockStatic(Bukkit.class); - Server server = mock(Server.class); - when(Bukkit.getServer()).thenReturn(server); - when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); - when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class)); - - // Create an Addon - addon = new AOneBlock(); - File jFile = new File("addon.jar"); - try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { - - // Copy over config file from src folder - Path fromPath = Paths.get("src/main/resources/config.yml"); - Path path = Paths.get("config.yml"); - Files.copy(fromPath, path); - - //Add the new files to the jar. - add(path, tempJarOutputStream); - - // Copy over panels file from src folder - fromPath = Paths.get("src/main/resources/panels/phases_panel.yml"); - path = Paths.get("panels"); - Files.createDirectory(path); - path = Paths.get("panels/phases_panel.yml"); - Files.copy(fromPath, path); - - //Add the new files to the jar. - add(path, tempJarOutputStream); - } - - File dataFolder = new File("addons/AOneBlock"); - addon.setDataFolder(dataFolder); - addon.setFile(jFile); - AddonDescription desc = new AddonDescription.Builder("bentobox", "aoneblock", "1.3").description("test").authors("tasty").build(); - addon.setDescription(desc); - // Addons manager - AddonsManager am = mock(AddonsManager.class); - when(plugin.getAddonsManager()).thenReturn(am); - - // Flags manager - when(plugin.getFlagsManager()).thenReturn(fm); - when(fm.getFlags()).thenReturn(Collections.emptyList()); - - } - - private void add(Path path, JarOutputStream tempJarOutputStream) throws FileNotFoundException, IOException { - try (FileInputStream fis = new FileInputStream(path.toFile())) { - byte[] buffer = new byte[1024]; - int bytesRead = 0; - JarEntry entry = new JarEntry(path.toString()); - tempJarOutputStream.putNextEntry(entry); - while((bytesRead = fis.read(buffer)) != -1) { - tempJarOutputStream.write(buffer, 0, bytesRead); - } - } - - } - - /** - * @throws java.lang.Exception - */ - @After - public void tearDown() throws Exception { - //new File("addon.jar").delete(); - new File("config.yml").delete(); - deleteAll(new File("addons")); - deleteAll(new File("panels")); - } - - private void deleteAll(File file) throws IOException { - if (file.exists()) { - Files.walk(file.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } - - } - - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#onEnable()}. - */ - @Test - public void testOnEnable() { - testOnLoad(); - addon.setState(State.ENABLED); - addon.onEnable(); - verify(plugin, never()).logError(anyString()); - assertNotEquals(State.DISABLED, addon.getState()); - assertNotNull(addon.getBlockListener()); - assertNotNull(addon.getOneBlockManager()); - - - - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#onLoad()}. - */ - @Test - public void testOnLoad() { - addon.onLoad(); - // Check that config.yml file has been saved - File check = new File("addons/AOneBlock","config.yml"); - assertTrue(check.exists()); - assertTrue(addon.getPlayerCommand().isPresent()); - assertTrue(addon.getAdminCommand().isPresent()); - - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#onReload()}. - */ - @Test - public void testOnReload() { - addon.onEnable(); - addon.onReload(); - // Check that config.yml file has been saved - File check = new File("addons/AOneBlock","config.yml"); - assertTrue(check.exists()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#createWorlds()}. - */ - @Test - public void testCreateWorlds() { - addon.onLoad(); - addon.createWorlds(); - verify(plugin).log("[aoneblock] Creating AOneBlock world ..."); - verify(plugin).log("[aoneblock] Creating AOneBlock's Nether..."); - verify(plugin).log("[aoneblock] Creating AOneBlock's End World..."); - - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#getSettings()}. - */ - @Test - public void testGetSettings() { - addon.onLoad(); - assertNotNull(addon.getSettings()); - - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#getWorldSettings()}. - */ - @Test - public void testGetWorldSettings() { - addon.onLoad(); - assertEquals(addon.getSettings(), addon.getWorldSettings()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#getOneBlocksIsland(world.bentobox.bentobox.database.objects.Island)}. - */ - @Test - public void testGetOneBlocksIsland() { - addon.onEnable(); - @NonNull - OneBlockIslands i = addon.getOneBlocksIsland(island); - assertEquals(island.getUniqueId(), i.getUniqueId()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#getOneBlockManager()}. - */ - @Test - public void testGetOneBlockManager() { - assertNull(addon.getOneBlockManager()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#getBlockListener()}. - */ - @Test - public void testGetBlockListener() { - assertNull(addon.getBlockListener()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#getPlaceholdersManager()}. - */ - @Test - public void testGetPlaceholdersManager() { - assertNull(addon.getPlaceholdersManager()); - } - - /** - * Test method for {@link world.bentobox.aoneblock.AOneBlock#getHoloListener()}. - */ - @Test - public void testGetHoloListener() { - assertNull(addon.getHoloListener()); - } + @Mock + private User user; + @Mock + private IslandsManager im; + @Mock + private Island island; + + private AOneBlock addon; + @Mock + private BentoBox plugin; + @Mock + private FlagsManager fm; + @Mock + private Settings settings; + @Mock + private PlaceholdersManager phm; + + private static AbstractDatabaseHandler h; + + @SuppressWarnings("unchecked") + @BeforeClass + public static void beforeClass() throws IllegalAccessException, InvocationTargetException, IntrospectionException { + // This has to be done beforeClass otherwise the tests will interfere with each + // other + h = mock(AbstractDatabaseHandler.class); + // Database + PowerMockito.mockStatic(DatabaseSetup.class); + DatabaseSetup dbSetup = mock(DatabaseSetup.class); + when(DatabaseSetup.getDatabase()).thenReturn(dbSetup); + when(dbSetup.getHandler(any())).thenReturn(h); + when(h.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true)); + } + + @After + public void tearDown() throws IOException { + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + deleteAll(new File("database")); + deleteAll(new File("database_backup")); + new File("config.yml").delete(); + deleteAll(new File("addons")); + deleteAll(new File("panels")); + } + + private void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger()); + + // The database type has to be created one line before the thenReturn() to work! + DatabaseType value = DatabaseType.JSON; + when(plugin.getSettings()).thenReturn(settings); + when(settings.getDatabaseType()).thenReturn(value); + when(plugin.getPlaceholdersManager()).thenReturn(phm); + // Placeholders + when(phm.replacePlaceholders(any(), anyString())).thenAnswer(a -> (String) a.getArgument(1, String.class)); + + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + // Player + Player p = mock(Player.class); + // Sometimes use Mockito.withSettings().verboseLogging() + when(user.isOp()).thenReturn(false); + UUID uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + User.setPlugin(plugin); + + // Island World Manager + IslandWorldManager iwm = mock(IslandWorldManager.class); + when(plugin.getIWM()).thenReturn(iwm); + + // Player has island to begin with + island = mock(Island.class); + when(im.getIsland(Mockito.any(), Mockito.any(UUID.class))).thenReturn(island); + when(plugin.getIslands()).thenReturn(im); + + // Locales + // Return the reference (USE THIS IN THE FUTURE) + when(user.getTranslation(Mockito.anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + + // Server + PowerMockito.mockStatic(Bukkit.class); + Server server = mock(Server.class); + when(Bukkit.getServer()).thenReturn(server); + when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); + when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class)); + + // Create an Addon + addon = new AOneBlock(); + File jFile = new File("addon.jar"); + try (JarOutputStream tempJarOutputStream = new JarOutputStream(new FileOutputStream(jFile))) { + + // Copy over config file from src folder + Path fromPath = Paths.get("src/main/resources/config.yml"); + Path path = Paths.get("config.yml"); + Files.copy(fromPath, path); + + // Add the new files to the jar. + add(path, tempJarOutputStream); + + // Copy over panels file from src folder + fromPath = Paths.get("src/main/resources/panels/phases_panel.yml"); + path = Paths.get("panels"); + Files.createDirectory(path); + path = Paths.get("panels/phases_panel.yml"); + Files.copy(fromPath, path); + + // Add the new files to the jar. + add(path, tempJarOutputStream); + } + + File dataFolder = new File("addons/AOneBlock"); + addon.setDataFolder(dataFolder); + addon.setFile(jFile); + AddonDescription desc = new AddonDescription.Builder("bentobox", "aoneblock", "1.3").description("test") + .authors("tasty").build(); + addon.setDescription(desc); + // Addons manager + AddonsManager am = mock(AddonsManager.class); + when(plugin.getAddonsManager()).thenReturn(am); + + // Flags manager + when(plugin.getFlagsManager()).thenReturn(fm); + when(fm.getFlags()).thenReturn(Collections.emptyList()); + + // RanksManager + RanksManager rm = new RanksManager(); + when(plugin.getRanksManager()).thenReturn(rm); + + } + + private void add(Path path, JarOutputStream tempJarOutputStream) throws FileNotFoundException, IOException { + try (FileInputStream fis = new FileInputStream(path.toFile())) { + byte[] buffer = new byte[1024]; + int bytesRead = 0; + JarEntry entry = new JarEntry(path.toString()); + tempJarOutputStream.putNextEntry(entry); + while ((bytesRead = fis.read(buffer)) != -1) { + tempJarOutputStream.write(buffer, 0, bytesRead); + } + } + + } + + /** + * Test method for {@link world.bentobox.aoneblock.AOneBlock#onEnable()}. + */ + @Test + public void testOnEnable() { + testOnLoad(); + addon.setState(State.ENABLED); + addon.onEnable(); + verify(plugin, never()).logError(anyString()); + assertNotEquals(State.DISABLED, addon.getState()); + assertNotNull(addon.getBlockListener()); + assertNotNull(addon.getOneBlockManager()); + + } + + /** + * Test method for {@link world.bentobox.aoneblock.AOneBlock#onLoad()}. + */ + @Test + public void testOnLoad() { + addon.onLoad(); + // Check that config.yml file has been saved + File check = new File("addons/AOneBlock", "config.yml"); + assertTrue(check.exists()); + assertTrue(addon.getPlayerCommand().isPresent()); + assertTrue(addon.getAdminCommand().isPresent()); + + } + + /** + * Test method for {@link world.bentobox.aoneblock.AOneBlock#onReload()}. + */ + @Test + public void testOnReload() { + addon.onEnable(); + addon.onReload(); + // Check that config.yml file has been saved + File check = new File("addons/AOneBlock", "config.yml"); + assertTrue(check.exists()); + } + + /** + * Test method for {@link world.bentobox.aoneblock.AOneBlock#createWorlds()}. + */ + @Test + public void testCreateWorlds() { + addon.onLoad(); + addon.createWorlds(); + verify(plugin).log("[aoneblock] Creating AOneBlock world ..."); + verify(plugin).log("[aoneblock] Creating AOneBlock's Nether..."); + verify(plugin).log("[aoneblock] Creating AOneBlock's End World..."); + + } + + /** + * Test method for {@link world.bentobox.aoneblock.AOneBlock#getSettings()}. + */ + @Test + public void testGetSettings() { + addon.onLoad(); + assertNotNull(addon.getSettings()); + + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.AOneBlock#getWorldSettings()}. + */ + @Test + public void testGetWorldSettings() { + addon.onLoad(); + assertEquals(addon.getSettings(), addon.getWorldSettings()); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.AOneBlock#getOneBlocksIsland(world.bentobox.bentobox.database.objects.Island)}. + */ + @Test + public void testGetOneBlocksIsland() { + addon.onEnable(); + @NonNull + OneBlockIslands i = addon.getOneBlocksIsland(island); + assertEquals(island.getUniqueId(), i.getUniqueId()); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.AOneBlock#getOneBlockManager()}. + */ + @Test + public void testGetOneBlockManager() { + assertNull(addon.getOneBlockManager()); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.AOneBlock#getBlockListener()}. + */ + @Test + public void testGetBlockListener() { + assertNull(addon.getBlockListener()); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.AOneBlock#getPlaceholdersManager()}. + */ + @Test + public void testGetPlaceholdersManager() { + assertNull(addon.getPlaceholdersManager()); + } + + /** + * Test method for {@link world.bentobox.aoneblock.AOneBlock#getHoloListener()}. + */ + @Test + public void testGetHoloListener() { + assertNull(addon.getHoloListener()); + } } diff --git a/src/test/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommandTest.java b/src/test/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommandTest.java index 4dd9bb7d..eabfb3a4 100644 --- a/src/test/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommandTest.java +++ b/src/test/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommandTest.java @@ -11,10 +11,18 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.beans.IntrospectionException; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import org.bukkit.Bukkit; import org.bukkit.World; @@ -22,10 +30,14 @@ import org.bukkit.entity.Player; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; @@ -39,6 +51,8 @@ import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.AbstractDatabaseHandler; +import world.bentobox.bentobox.database.DatabaseSetup; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.managers.CommandsManager; import world.bentobox.bentobox.managers.IslandWorldManager; @@ -51,126 +65,156 @@ * */ @RunWith(PowerMockRunner.class) -@PrepareForTest({Bukkit.class, BentoBox.class, User.class}) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, DatabaseSetup.class }) public class IslandSetCountCommandTest { - @Mock - private BentoBox plugin; - @Mock - private CompositeCommand ac; - @Mock - private User user; - @Mock - private LocalesManager lm; - @Mock - private AOneBlock addon; - private UUID uuid; - @Mock - private World world; - @Mock - private IslandsManager im; - @Mock - private @Nullable Island island; - @Mock - private IslandWorldManager iwm; - private IslandSetCountCommand iscc; - @Mock - private BlockListener bl; - private @NonNull OneBlockIslands oneBlockIsland = new OneBlockIslands(UUID.randomUUID().toString()); - - - /** - * @throws java.lang.Exception - */ - @Before - public void setUp() throws Exception { - // Set up plugin - BentoBox plugin = mock(BentoBox.class); - Whitebox.setInternalState(BentoBox.class, "instance", plugin); - - // Command manager - CommandsManager cm = mock(CommandsManager.class); - when(plugin.getCommandsManager()).thenReturn(cm); - - // Player - Player p = mock(Player.class); - // Sometimes use Mockito.withSettings().verboseLogging() - when(user.isOp()).thenReturn(false); - when(user.getPermissionValue(anyString(), anyInt())).thenReturn(4); - when(user.getWorld()).thenReturn(world); - uuid = UUID.randomUUID(); - when(user.getUniqueId()).thenReturn(uuid); - when(user.getPlayer()).thenReturn(p); - when(user.getName()).thenReturn("tastybento"); - when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); - User.setPlugin(plugin); - - // Parent command has no aliases - when(ac.getSubCommandAliases()).thenReturn(new HashMap<>()); - when(ac.getWorld()).thenReturn(world); - when(ac.getAddon()).thenReturn(addon); - - // Islands - when(plugin.getIslands()).thenReturn(im); - when(im.getIsland(world, user)).thenReturn(island); - when(im.hasIsland(world, user)).thenReturn(true); - when(im.inTeam(world, uuid)).thenReturn(true); - when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK); - when(island.getRank(user)).thenReturn(RanksManager.MEMBER_RANK); - - - // IWM - when(plugin.getIWM()).thenReturn(iwm); - when(iwm.getPermissionPrefix(any())).thenReturn("bskyblock."); - - // Settings - Settings settings = new Settings(); - when(addon.getSettings()).thenReturn(settings); - - // RanksManager - RanksManager rm = new RanksManager(); - when(plugin.getRanksManager()).thenReturn(rm); - - // BlockListener - when(addon.getBlockListener()).thenReturn(bl); - when(bl.getIsland(island)).thenReturn(oneBlockIsland); - - - // DUT - iscc = new IslandSetCountCommand(this.ac, - settings.getSetCountCommand().split(" ")[0], - settings.getSetCountCommand().split(" ")); - } + @Mock + private BentoBox plugin; + @Mock + private CompositeCommand ac; + @Mock + private User user; + @Mock + private LocalesManager lm; + @Mock + private AOneBlock addon; + private UUID uuid; + @Mock + private World world; + @Mock + private IslandsManager im; + @Mock + private @Nullable Island island; + @Mock + private IslandWorldManager iwm; + private IslandSetCountCommand iscc; + @Mock + private BlockListener bl; + private @NonNull OneBlockIslands oneBlockIsland = new OneBlockIslands(UUID.randomUUID().toString()); - /** - * Test method for {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#IslandSetCountCommand(world.bentobox.bentobox.api.commands.CompositeCommand, java.lang.String, java.lang.String[])}. - */ - @Test - public void testIslandSetCountCommand() { - assertNotNull(iscc); - } + private static AbstractDatabaseHandler h; - /** - * Test method for {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#setup()}. - */ - @Test - public void testSetup() { - assertEquals("island.setcount", iscc.getPermission()); - assertEquals("aoneblock.commands.island.setcount.parameters", iscc.getParameters()); - assertEquals("aoneblock.commands.island.setcount.description", iscc.getDescription()); - assertTrue(iscc.isConfigurableRankCommand()); - assertTrue(iscc.isOnlyPlayer()); + @SuppressWarnings("unchecked") + @BeforeClass + public static void beforeClass() throws IllegalAccessException, InvocationTargetException, IntrospectionException { + // This has to be done beforeClass otherwise the tests will interfere with each + // other + h = mock(AbstractDatabaseHandler.class); + // Database + PowerMockito.mockStatic(DatabaseSetup.class); + DatabaseSetup dbSetup = mock(DatabaseSetup.class); + when(DatabaseSetup.getDatabase()).thenReturn(dbSetup); + when(dbSetup.getHandler(any())).thenReturn(h); + when(h.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true)); + } - } - - /** - * Test method for {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - public void testExecuteUserStringListOfStringShowHelp() { - assertFalse(iscc.execute(user, "", Collections.emptyList())); - verify(user).sendMessage("commands.help.header","[label]",null); - } - - /** + @After + public void tearDown() throws IOException { + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + deleteAll(new File("database")); + deleteAll(new File("database_backup")); + } + + private void deleteAll(File file) throws IOException { + if (file.exists()) { + Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + + } + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + // Set up plugin + BentoBox plugin = mock(BentoBox.class); + Whitebox.setInternalState(BentoBox.class, "instance", plugin); + + // Command manager + CommandsManager cm = mock(CommandsManager.class); + when(plugin.getCommandsManager()).thenReturn(cm); + + // Player + Player p = mock(Player.class); + // Sometimes use Mockito.withSettings().verboseLogging() + when(user.isOp()).thenReturn(false); + when(user.getPermissionValue(anyString(), anyInt())).thenReturn(4); + when(user.getWorld()).thenReturn(world); + uuid = UUID.randomUUID(); + when(user.getUniqueId()).thenReturn(uuid); + when(user.getPlayer()).thenReturn(p); + when(user.getName()).thenReturn("tastybento"); + when(user.getTranslation(any())).thenAnswer(invocation -> invocation.getArgument(0, String.class)); + User.setPlugin(plugin); + + // Parent command has no aliases + when(ac.getSubCommandAliases()).thenReturn(new HashMap<>()); + when(ac.getWorld()).thenReturn(world); + when(ac.getAddon()).thenReturn(addon); + + // Islands + when(plugin.getIslands()).thenReturn(im); + when(im.getIsland(world, user)).thenReturn(island); + when(im.hasIsland(world, user)).thenReturn(true); + when(im.inTeam(world, uuid)).thenReturn(true); + when(island.getRankCommand(anyString())).thenReturn(RanksManager.OWNER_RANK); + when(island.getRank(user)).thenReturn(RanksManager.MEMBER_RANK); + + // IWM + when(plugin.getIWM()).thenReturn(iwm); + when(iwm.getPermissionPrefix(any())).thenReturn("bskyblock."); + + // Settings + Settings settings = new Settings(); + when(addon.getSettings()).thenReturn(settings); + + // RanksManager + RanksManager rm = new RanksManager(); + when(plugin.getRanksManager()).thenReturn(rm); + + // BlockListener + when(addon.getBlockListener()).thenReturn(bl); + when(bl.getIsland(island)).thenReturn(oneBlockIsland); + + // DUT + iscc = new IslandSetCountCommand(this.ac, settings.getSetCountCommand().split(" ")[0], + settings.getSetCountCommand().split(" ")); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#IslandSetCountCommand(world.bentobox.bentobox.api.commands.CompositeCommand, java.lang.String, java.lang.String[])}. + */ + @Test + public void testIslandSetCountCommand() { + assertNotNull(iscc); + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#setup()}. + */ + @Test + public void testSetup() { + assertEquals("island.setcount", iscc.getPermission()); + assertEquals("aoneblock.commands.island.setcount.parameters", iscc.getParameters()); + assertEquals("aoneblock.commands.island.setcount.description", iscc.getDescription()); + assertTrue(iscc.isConfigurableRankCommand()); + assertTrue(iscc.isOnlyPlayer()); + + } + + /** + * Test method for + * {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + public void testExecuteUserStringListOfStringShowHelp() { + assertFalse(iscc.execute(user, "", Collections.emptyList())); + verify(user).sendMessage("commands.help.header", "[label]", null); + } + + /** * Test method for {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. */ @Test @@ -180,17 +224,18 @@ public void testExecuteUserStringListOfStringNoIsland() { assertFalse(iscc.execute(user, "", List.of("2000"))); verify(user).sendMessage("general.errors.no-island"); } - - /** - * Test method for {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. - */ - @Test - public void testExecuteUserStringListOfStringLowRank() { - assertFalse(iscc.execute(user, "", List.of("2000"))); - verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, RanksManager.MEMBER_RANK_REF); - } - - /** + + /** + * Test method for + * {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. + */ + @Test + public void testExecuteUserStringListOfStringLowRank() { + assertFalse(iscc.execute(user, "", List.of("2000"))); + verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, RanksManager.MEMBER_RANK_REF); + } + + /** * Test method for {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. */ @Test @@ -199,8 +244,8 @@ public void testExecuteUserStringListOfStringRankOKNegativeCount() { assertFalse(iscc.execute(user, "", List.of("-2000"))); verify(user).sendMessage("general.errors.must-be-positive-number", TextVariables.NUMBER, "-2000"); } - - /** + + /** * Test method for {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. */ @Test @@ -211,7 +256,7 @@ public void testExecuteUserStringListOfStringRankOKTooHighCount() { verify(user).sendMessage("aoneblock.commands.island.setcount.too-high", TextVariables.NUMBER, "0"); } - /** + /** * Test method for {@link world.bentobox.aoneblock.commands.island.IslandSetCountCommand#execute(world.bentobox.bentobox.api.user.User, java.lang.String, java.util.List)}. */ @Test @@ -226,5 +271,4 @@ public void testExecuteUserStringListOfString() { } - } From cef02f47128a9d6d073be425a6fb68e0463137b0 Mon Sep 17 00:00:00 2001 From: Baterka Date: Fri, 24 Nov 2023 14:08:39 +0100 Subject: [PATCH 25/42] Fixed issues: - Everytime new phase was set, plugin sent the first block of the phase 2 times. - CustomBlocks in fixedBlocks were causing issues where it was pushing the CustomBlock multiple times into queue. - Phase config was not refreshed when doing /isa reload. I tested it and I do not see any issues. Refactors: - Variable naming refactors, because having name `obPhase` for `phase` instance + name `phase` for the `ConfigurationSection` was not really understandable (I changed it to `phaseConfig`) Added features: - New `CustomBlock` for mobs in format: ``` - type: mob mob: ZOMBIE underlying-block: GRASS_BLOCK ``` It will spawn a mob on top of underlying block. I know that mob spawns are not exactly counted as "destroyed block", but in this case it is needed. It would not make sense setting it in fixedBlocks if not. --- .../world/bentobox/aoneblock/AOneBlock.java | 1 + .../dataobjects/OneBlockIslands.java | 12 +- .../aoneblock/listeners/BlockListener.java | 5 +- .../aoneblock/listeners/MakeSpace.java | 2 +- .../oneblocks/OneBlockCustomBlock.java | 7 +- .../oneblocks/OneBlockCustomBlockCreator.java | 2 + .../aoneblock/oneblocks/OneBlocksManager.java | 303 +++++++++--------- .../customblock/BlockDataCustomBlock.java | 3 +- .../oneblocks/customblock/MobCustomBlock.java | 96 ++++++ 9 files changed, 275 insertions(+), 156 deletions(-) create mode 100644 src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java diff --git a/src/main/java/world/bentobox/aoneblock/AOneBlock.java b/src/main/java/world/bentobox/aoneblock/AOneBlock.java index 89a1d0b2..0f646715 100644 --- a/src/main/java/world/bentobox/aoneblock/AOneBlock.java +++ b/src/main/java/world/bentobox/aoneblock/AOneBlock.java @@ -172,6 +172,7 @@ public void onReload() { blockListener.saveCache(); if (loadSettings()) { log("Reloaded AOneBlock settings"); + loadData(); } } diff --git a/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java b/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java index 9831ad54..ff4c4912 100644 --- a/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java +++ b/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java @@ -10,6 +10,7 @@ import com.google.gson.annotations.Expose; import world.bentobox.aoneblock.oneblocks.OneBlockObject; +import world.bentobox.aoneblock.oneblocks.customblock.MobCustomBlock; import world.bentobox.bentobox.database.objects.DataObject; import world.bentobox.bentobox.database.objects.Table; @@ -135,7 +136,16 @@ public Queue getQueue() { * @return list of upcoming mobs */ public List getNearestMob(int i) { - return getQueue().stream().limit(i).filter(OneBlockObject::isEntity).map(OneBlockObject::getEntityType).toList(); + return getQueue().stream().limit(i).filter(obo -> + // Include OneBlockObjects that are Entity, or custom block of type MobCustomBlock + obo.isEntity() || (obo.isCustomBlock() && obo.getCustomBlock() instanceof MobCustomBlock) + ).map(obo -> { + if (obo.isCustomBlock() && obo.getCustomBlock() instanceof MobCustomBlock) { + return ((MobCustomBlock) obo.getCustomBlock()).getMob(); + } + + return obo.getEntityType(); + }).toList(); } /** diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java index 94553c2c..50a7d78e 100644 --- a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java +++ b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java @@ -295,7 +295,8 @@ private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player is.clearQueue(); } // Get the block number in this phase - int blockNumber = is.getBlockNumber() - phase.getBlockNumberValue() + (int) is.getQueue().stream().filter(OneBlockObject::isMaterial).count(); + int materialBlocksInQueue = (int) is.getQueue().stream().filter(obo -> obo.isMaterial() || obo.isCustomBlock()).count(); + int blockNumber = is.getBlockNumber() - (phase.getBlockNumberValue() - 1) + materialBlocksInQueue; // Get the block that is being broken Block block = Objects.requireNonNull(i.getCenter()).toVector().toLocation(world).getBlock(); // Fill a 5 block queue @@ -406,7 +407,7 @@ else if (player.getLocation().getBlock().equals(block.getRelative(BlockFace.UP)) private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block) { if (nextBlock.isCustomBlock()) { - nextBlock.getCustomBlock().setBlock(block); + nextBlock.getCustomBlock().execute(addon, block); } else if (nextBlock.isItemsAdderBlock()) { //Get Custom Block from ItemsAdder and place it CustomBlock cBlock = CustomBlock.getInstance(nextBlock.getItemsAdderBlock()); diff --git a/src/main/java/world/bentobox/aoneblock/listeners/MakeSpace.java b/src/main/java/world/bentobox/aoneblock/listeners/MakeSpace.java index 75d2dce1..5dfa914a 100644 --- a/src/main/java/world/bentobox/aoneblock/listeners/MakeSpace.java +++ b/src/main/java/world/bentobox/aoneblock/listeners/MakeSpace.java @@ -52,7 +52,7 @@ public MakeSpace(AOneBlock addon) { * @param entity Entity that is spawned. * @param spawnLocation Location where entity is spawned. */ - void makeSpace(@NonNull Entity entity, @NonNull Location spawnLocation) + public void makeSpace(@NonNull Entity entity, @NonNull Location spawnLocation) { World world = entity.getWorld(); List airBlocks = new ArrayList<>(); diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlock.java index 40560508..4c30b8d8 100644 --- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlock.java +++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlock.java @@ -1,17 +1,18 @@ package world.bentobox.aoneblock.oneblocks; import org.bukkit.block.Block; +import world.bentobox.aoneblock.AOneBlock; /** - * Represents a custom block + * Represents a custom block with custom executable * * @author HSGamer */ public interface OneBlockCustomBlock { /** - * Set the block + * Executes the custom logic * * @param block the block */ - void setBlock(Block block); + void execute(AOneBlock addon, Block block); } diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlockCreator.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlockCreator.java index 36cb8af2..91d8c040 100644 --- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlockCreator.java +++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlockCreator.java @@ -1,6 +1,7 @@ package world.bentobox.aoneblock.oneblocks; import world.bentobox.aoneblock.oneblocks.customblock.BlockDataCustomBlock; +import world.bentobox.aoneblock.oneblocks.customblock.MobCustomBlock; import java.util.*; import java.util.function.Function; @@ -16,6 +17,7 @@ public final class OneBlockCustomBlockCreator { static { register("block-data", BlockDataCustomBlock::fromMap); + register("mob", MobCustomBlock::fromMap); register("short", map -> { String type = Objects.toString(map.get("data"), null); if (type == null) { diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java index 361573ec..af67be18 100644 --- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java +++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java @@ -128,154 +128,161 @@ void copyPhasesFromAddonJar(File file) { } } - private void loadPhase(File phaseFile) throws IOException { - addon.log("Loading " + phaseFile.getName()); - // Load the config file - YamlConfiguration oneBlocks = new YamlConfiguration(); - try { - oneBlocks.load(phaseFile); - } catch (Exception e) { - addon.logError(e.getMessage()); - return; - } - for (String blockNumber : oneBlocks.getKeys(false)) { - Integer blockNum = Integer.valueOf(blockNumber); - OneBlockPhase obPhase = blockProbs.computeIfAbsent(blockNum, k -> new OneBlockPhase(blockNumber)); - // Get config Section - ConfigurationSection phase = oneBlocks.getConfigurationSection(blockNumber); - // goto - if (phase.contains(GOTO_BLOCK)) { - obPhase.setGotoBlock(phase.getInt(GOTO_BLOCK, 0)); - continue; - } - initBlock(blockNumber, obPhase, phase); - // Blocks - addBlocks(obPhase, phase); - // Mobs - addMobs(obPhase, phase); - // Chests - addChests(obPhase, phase); - // Commands - addCommands(obPhase, phase); - // Requirements - addRequirements(obPhase, phase); - // Add to the map - blockProbs.put(blockNum, obPhase); - } - } - - /** - * Load in the phase's init - * - * @param blockNumber string representation of this phase's block number - * @param obPhase OneBlockPhase - * @param phase configuration section being read - * @throws IOException if there's an error in the config file - */ - void initBlock(String blockNumber, OneBlockPhase obPhase, ConfigurationSection phase) throws IOException { - if (phase.contains(NAME, true)) { - if (obPhase.getPhaseName() != null) { - throw new IOException( - "Block " + blockNumber + ": Phase name trying to be set to " + phase.getString(NAME) - + " but already set to " + obPhase.getPhaseName() + ". Duplicate phase file?"); - } - // name - obPhase.setPhaseName(phase.getString(NAME, blockNumber)); - } - // biome - if (phase.contains(BIOME, true)) { - if (obPhase.getPhaseBiome() != null) { - throw new IOException("Block " + blockNumber + ": Biome trying to be set to " + phase.getString(BIOME) - + " but already set to " + obPhase.getPhaseBiome() + " Duplicate phase file?"); - } - obPhase.setPhaseBiome(getBiome(phase.getString(BIOME))); - } - // First block - if (phase.contains(FIRST_BLOCK)) { - if (obPhase.getFirstBlock() != null) { - throw new IOException( - "Block " + blockNumber + ": First block trying to be set to " + phase.getString(FIRST_BLOCK) - + " but already set to " + obPhase.getFirstBlock() + " Duplicate phase file?"); - } - addFirstBlock(obPhase, phase.getString(FIRST_BLOCK)); - } - // Icon block - if (phase.contains(ICON)) { - ItemStack icon = ItemParser.parse(phase.getString(ICON)); - - if (icon == null) { - throw new IOException("ItemParser failed to parse icon: '" + phase.getString(ICON) + "' for phase " - + obPhase.getFirstBlock() + ". Can you check if it is correct?"); - } - - obPhase.setIconBlock(icon); - } - // First blocks - if (phase.contains(FIXED_BLOCKS)) { - if (!obPhase.getFixedBlocks().isEmpty()) { - throw new IOException( - "Block " + blockNumber + ": Fixed blocks trying to be set to " + phase.getString(FIXED_BLOCKS) - + " but already set to " + obPhase.getFixedBlocks() + " Duplicate phase file?"); - } - addFixedBlocks(obPhase, phase.getConfigurationSection(FIXED_BLOCKS)); - } - - if (phase.contains(HOLOGRAMS)) { - if (!obPhase.getHologramLines().isEmpty()) { - throw new IOException( - "Block " + blockNumber + ": Hologram Lines trying to be set to " + phase.getString(HOLOGRAMS) - + " but already set to " + obPhase.getHologramLines() + " Duplicate phase file?"); - } - addHologramLines(obPhase, phase.getConfigurationSection(HOLOGRAMS)); - } - } - - private void addFixedBlocks(OneBlockPhase obPhase, ConfigurationSection fb) { - if (fb == null) { - return; - } - Map result = new HashMap<>(); - for (String key : fb.getKeys(false)) { - if (!NumberUtils.isNumber(key)) { - addon.logError("Fixed block key must be an integer. " + key); - continue; - } - int k = Integer.parseInt(key); - if (fb.isConfigurationSection(key)) { - Map map = fb.getConfigurationSection(key).getValues(false); - Optional customBlock = OneBlockCustomBlockCreator.create(map); - if (customBlock.isPresent()) { - result.put(k, new OneBlockObject(customBlock.get(), 0)); - } else { - addon.logError("Fixed block key " + key + " material is not a valid custom block. Ignoring."); - } - } else { - String mat = fb.getString(key); - if (mat == null) { - continue; - } - - Optional customBlock = OneBlockCustomBlockCreator.create(mat); - if (customBlock.isPresent()) { - result.put(k, new OneBlockObject(customBlock.get(), 0)); - } else { - Material m = Material.matchMaterial(mat); - if (m != null && m.isBlock()) { - result.put(k, new OneBlockObject(m, 0)); - } else { - addon.logError("Fixed block key " + key + " material is invalid or not a block. Ignoring."); - } - } - } - } - // Set the first block if it exists - if (result.containsKey(0)) { - obPhase.setFirstBlock(result.get(0)); - } - // Store the remainder - obPhase.setFixedBlocks(result); - - } + private void loadPhase(File phaseFile) throws IOException { + addon.log("Loading " + phaseFile.getName()); + // Load the config file + YamlConfiguration oneBlocks = new YamlConfiguration(); + try { + oneBlocks.load(phaseFile); + } catch (Exception e) { + addon.logError(e.getMessage()); + return; + } + for (String phaseStartBlockNumKey : oneBlocks.getKeys(false)) { + Integer phaseStartBlockNum = Integer.valueOf(phaseStartBlockNumKey); + OneBlockPhase obPhase = blockProbs.computeIfAbsent(phaseStartBlockNum, k -> new OneBlockPhase(phaseStartBlockNumKey)); + // Get config Section + ConfigurationSection phaseConfig = oneBlocks.getConfigurationSection(phaseStartBlockNumKey); + // goto + if (phaseConfig.contains(GOTO_BLOCK)) { + obPhase.setGotoBlock(phaseConfig.getInt(GOTO_BLOCK, 0)); + continue; + } + initBlock(phaseStartBlockNumKey, obPhase, phaseConfig); + // Blocks + addBlocks(obPhase, phaseConfig); + // Mobs + addMobs(obPhase, phaseConfig); + // Chests + addChests(obPhase, phaseConfig); + // Commands + addCommands(obPhase, phaseConfig); + // Requirements + addRequirements(obPhase, phaseConfig); + // Add to the map + blockProbs.put(phaseStartBlockNum, obPhase); + } + } + + /** + * Load in the phase's init + * + * @param blockNumber string representation of this phase's block number + * @param obPhase OneBlockPhase + * @param phaseConfig configuration section being read + * @throws IOException if there's an error in the config file + */ + void initBlock(String blockNumber, OneBlockPhase obPhase, ConfigurationSection phaseConfig) throws IOException { + // Set name + if (phaseConfig.contains(NAME, true)) { + if (obPhase.getPhaseName() != null) { + throw new IOException( + "Block " + blockNumber + ": Phase name trying to be set to " + phaseConfig.getString(NAME) + + " but already set to " + obPhase.getPhaseName() + ". Duplicate phase file?"); + } + obPhase.setPhaseName(phaseConfig.getString(NAME, blockNumber)); + } + + // Set biome + if (phaseConfig.contains(BIOME, true)) { + if (obPhase.getPhaseBiome() != null) { + throw new IOException("Block " + blockNumber + ": Biome trying to be set to " + phaseConfig.getString(BIOME) + + " but already set to " + obPhase.getPhaseBiome() + " Duplicate phase file?"); + } + obPhase.setPhaseBiome(getBiome(phaseConfig.getString(BIOME))); + } + + // Set first block + if (phaseConfig.contains(FIRST_BLOCK)) { + if (obPhase.getFirstBlock() != null) { + throw new IOException( + "Block " + blockNumber + ": First block trying to be set to " + phaseConfig.getString(FIRST_BLOCK) + + " but already set to " + obPhase.getFirstBlock() + " Duplicate phase file?"); + } + addFirstBlock(obPhase, phaseConfig.getString(FIRST_BLOCK)); + } + + // Set icon + if (phaseConfig.contains(ICON)) { + ItemStack icon = ItemParser.parse(phaseConfig.getString(ICON)); + + if (icon == null) { + throw new IOException("ItemParser failed to parse icon: '" + phaseConfig.getString(ICON) + "' for phase " + + obPhase.getFirstBlock() + ". Can you check if it is correct?"); + } + + obPhase.setIconBlock(icon); + } + + // Add fixed blocks + if (phaseConfig.contains(FIXED_BLOCKS)) { + if (!obPhase.getFixedBlocks().isEmpty()) { + throw new IOException( + "Block " + blockNumber + ": Fixed blocks trying to be set to " + phaseConfig.getString(FIXED_BLOCKS) + + " but already set to " + obPhase.getFixedBlocks() + " Duplicate phase file?"); + } + addFixedBlocks(obPhase, phaseConfig.getConfigurationSection(FIXED_BLOCKS)); + } + + // Add holograms + if (phaseConfig.contains(HOLOGRAMS)) { + if (!obPhase.getHologramLines().isEmpty()) { + throw new IOException( + "Block " + blockNumber + ": Hologram Lines trying to be set to " + phaseConfig.getString(HOLOGRAMS) + + " but already set to " + obPhase.getHologramLines() + " Duplicate phase file?"); + } + addHologramLines(obPhase, phaseConfig.getConfigurationSection(HOLOGRAMS)); + } + } + + private void addFixedBlocks(OneBlockPhase obPhase, ConfigurationSection firstBlocksConfig) { + if (firstBlocksConfig == null) { + return; + } + Map result = new HashMap<>(); + for (String key : firstBlocksConfig.getKeys(false)) { + if (!NumberUtils.isNumber(key)) { + addon.logError("Fixed block key must be an integer. " + key); + continue; + } + int k = Integer.parseInt(key); + + if (firstBlocksConfig.isConfigurationSection(key)) { // Item value is object + Map map = firstBlocksConfig.getConfigurationSection(key).getValues(false); + Optional customBlock = OneBlockCustomBlockCreator.create(map); + if (customBlock.isPresent()) { + result.put(k, new OneBlockObject(customBlock.get(), 0)); + } else { + addon.logError("Fixed block key " + key + " material is not a valid custom block. Ignoring."); + } + } else { // Item value is string + String mat = firstBlocksConfig.getString(key); + if (mat == null) { + continue; + } + + Optional customBlock = OneBlockCustomBlockCreator.create(mat); + if (customBlock.isPresent()) { + result.put(k, new OneBlockObject(customBlock.get(), 0)); + } else { + Material m = Material.matchMaterial(mat); + if (m != null && m.isBlock()) { + result.put(k, new OneBlockObject(m, 0)); + } else { + addon.logError("Fixed block key " + key + " material is invalid or not a block. Ignoring."); + } + } + } + } + // Set the first block if it exists + if (result.containsKey(0)) { + addon.log("Found firstBlock in fixedBlocks."); + obPhase.setFirstBlock(result.get(0)); + } + // Store the remainder + obPhase.setFixedBlocks(result); + + } private void addHologramLines(OneBlockPhase obPhase, ConfigurationSection fb) { if (fb == null) diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java index fb9004df..1f777022 100644 --- a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java +++ b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java @@ -7,6 +7,7 @@ import org.bukkit.Bukkit; import org.bukkit.block.Block; +import world.bentobox.aoneblock.AOneBlock; import world.bentobox.aoneblock.oneblocks.OneBlockCustomBlock; import world.bentobox.bentobox.BentoBox; @@ -31,7 +32,7 @@ public static Optional fromMap(Map map) { } @Override - public void setBlock(Block block) { + public void execute(AOneBlock addon, Block block) { try { block.setBlockData(Bukkit.createBlockData(blockData)); } catch (IllegalArgumentException e) { diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java new file mode 100644 index 00000000..9f762808 --- /dev/null +++ b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java @@ -0,0 +1,96 @@ +package world.bentobox.aoneblock.oneblocks.customblock; + +import com.google.common.base.Enums; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.NonNull; +import world.bentobox.aoneblock.AOneBlock; +import world.bentobox.aoneblock.listeners.MakeSpace; +import world.bentobox.aoneblock.oneblocks.OneBlockCustomBlock; +import world.bentobox.bentobox.BentoBox; + +import java.util.*; + +/** + * A custom block that spawns mob on an underlying block + * + * @author HSGamer + */ +public class MobCustomBlock implements OneBlockCustomBlock { + private final EntityType mob; + private final Material underlyingBlock; + + public MobCustomBlock(EntityType mob, Material underlyingBlock) { + this.mob = mob; + this.underlyingBlock = underlyingBlock; + } + + public static Optional fromMap(Map map) { + String entityTypeValue = Objects.toString(map.get("mob"), null); + String underlyingBlockValue = Objects.toString(map.get("underlying-block"), null); + + EntityType entityType = maybeEntity(entityTypeValue); + Material underlyingBlock = Material.getMaterial(underlyingBlockValue); + + if(underlyingBlock == null){ + BentoBox.getInstance().logWarning("Underlying block " + underlyingBlockValue + " does not exist and will be replaced with STONE."); + } + + return Optional.of(new MobCustomBlock(entityType, underlyingBlock)); + } + + private static EntityType maybeEntity(String entityTypeValue) { + String name = entityTypeValue.toUpperCase(Locale.ENGLISH); + EntityType et; + + // Pig zombie handling + if (name.equals("PIG_ZOMBIE") || name.equals("ZOMBIFIED_PIGLIN")) { + et = Enums.getIfPresent(EntityType.class, "ZOMBIFIED_PIGLIN") + .or(Enums.getIfPresent(EntityType.class, "PIG_ZOMBIE").or(EntityType.PIG)); + } else { + et = Enums.getIfPresent(EntityType.class, name).orNull(); + } + + if (et == null) { + // Does not exist + BentoBox.getInstance().logWarning("Entity " + name + " does not exist and will not spawn when block is shown."); + return null; + } + if (et.isSpawnable() && et.isAlive()) { + return et; + } else { + // Not spawnable + BentoBox.getInstance().logWarning("Entity " + et.name() + " is not spawnable and will not spawn when block is shown."); + return null; + } + } + + @Override + public void execute(AOneBlock addon, Block block) { + try { + block.setType(Objects.requireNonNullElse(underlyingBlock, Material.STONE)); + spawnEntity(addon, block, mob); + } catch (Exception e) { + BentoBox.getInstance().logError("Could not spawn entity " + mob.name() + " on block " + block.getType()); + } + } + + private void spawnEntity(AOneBlock addon, @NonNull Block block, @NonNull EntityType mob) { + Location spawnLoc = block.getLocation().add(new Vector(0.5D, 1D, 0.5D)); + Entity entity = block.getWorld().spawnEntity(spawnLoc, mob); + // Make space for entity - this will blot out blocks + if (addon.getSettings().isClearBlocks()) { + new MakeSpace(addon).makeSpace(entity, spawnLoc); + } + block.getWorld().playSound(block.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1F, 2F); + } + + public EntityType getMob() { + return mob; + } +} From 21fd05b5d28d8fee038081e856abe9fe3ec8f556 Mon Sep 17 00:00:00 2001 From: Baterka Date: Fri, 24 Nov 2023 15:55:39 +0100 Subject: [PATCH 26/42] Added features: - New `Requirement - cooldown` (seconds) that blocks new phase until time elapses. --- .../aoneblock/dataobjects/OneBlockIslands.java | 18 ++++++++++++++++++ .../aoneblock/listeners/BlockListener.java | 3 ++- .../aoneblock/listeners/CheckPhase.java | 10 +++++++++- .../aoneblock/oneblocks/Requirement.java | 13 ++++++++++++- .../bentobox/aoneblock/panels/PhasesPanel.java | 6 ++++-- src/main/resources/locales/cs.yml | 3 ++- src/main/resources/locales/de.yml | 1 + src/main/resources/locales/en-US.yml | 3 ++- src/main/resources/locales/es.yml | 1 + src/main/resources/locales/fr.yml | 1 + src/main/resources/locales/hr.yml | 1 + src/main/resources/locales/hu.yml | 1 + src/main/resources/locales/ja.yml | 1 + src/main/resources/locales/pl.yml | 1 + src/main/resources/locales/ru.yml | 1 + src/main/resources/locales/tr.yml | 1 + src/main/resources/locales/vi.yml | 1 + src/main/resources/locales/zh-CN.yml | 1 + src/main/resources/locales/zh-TW.yml | 1 + src/main/resources/phases/0_plains.yml | 1 + 20 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java b/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java index ff4c4912..4812defe 100644 --- a/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java +++ b/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java @@ -44,6 +44,12 @@ public class OneBlockIslands implements DataObject { @Expose private String hologram = ""; + /** + * Timestamp of last phase change + */ + @Expose + private long lastPhaseChangeTime = 0; + private Queue queue = new LinkedList<>(); /** @@ -196,5 +202,17 @@ public void setLifetime(long lifetime) { this.lifetime = lifetime; } + /** + * @return Timestamp of last phase change + */ + public long getLastPhaseChangeTime() { + return this.lastPhaseChangeTime; + } + /** + * @param lastPhaseChangeTime Timestamp of last phase change + */ + public void setLastPhaseChangeTime(long lastPhaseChangeTime) { + this.lastPhaseChangeTime = lastPhaseChangeTime; + } } diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java index 50a7d78e..fc3ca583 100644 --- a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java +++ b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java @@ -287,12 +287,13 @@ private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player saveIsland(i); } // Check if requirements met - if (check.phaseRequirementsFail(player, i, phase, world)) { + if (check.phaseRequirementsFail(player, i, is, phase, world)) { e.setCancelled(true); return; } if (newPhase) { is.clearQueue(); + is.setLastPhaseChangeTime(System.currentTimeMillis()); } // Get the block number in this phase int materialBlocksInQueue = (int) is.getQueue().stream().filter(obo -> obo.isMaterial() || obo.isCustomBlock()).count(); diff --git a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java index 7cb0a734..5a72bb6a 100644 --- a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java +++ b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java @@ -52,7 +52,7 @@ public CheckPhase(AOneBlock addon, BlockListener blockListener) { * @param world - world * @return true if the player cannot proceed to the next phase. */ - protected boolean phaseRequirementsFail(@Nullable Player player, @NonNull Island i, OneBlockPhase phase, @NonNull World world) { + protected boolean phaseRequirementsFail(@Nullable Player player, @NonNull Island i, @NonNull OneBlockIslands is, OneBlockPhase phase, @NonNull World world) { if (phase.getRequirements().isEmpty()) { return false; } @@ -88,6 +88,14 @@ protected boolean phaseRequirementsFail(@Nullable Player player, @NonNull Island } yield false; } + case COOLDOWN -> { + long remainingTime = r.getCooldown() - (System.currentTimeMillis() - is.getLastPhaseChangeTime()) / 1000; + if(remainingTime > 0){ + User.getInstance(player).sendMessage("aoneblock.phase.cooldown", TextVariables.NUMBER, String.valueOf(remainingTime)); + yield true; + } + yield false; + } }; if (b) blocked = true; } diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java b/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java index ffb324de..cc4f62e0 100644 --- a/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java +++ b/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java @@ -26,7 +26,11 @@ public enum ReqType { /** * Permission */ - PERMISSION("permission", String.class); + PERMISSION("permission", String.class), + /** + * Cooldown + */ + COOLDOWN("cooldown", Long.class); private final String key; private final Class clazz; @@ -79,6 +83,13 @@ public String getPermission() { return (String)requirement; } + /** + * @return the cooldown + */ + public long getCooldown() { + return (long)requirement; + } + /** * @return the type */ diff --git a/src/main/java/world/bentobox/aoneblock/panels/PhasesPanel.java b/src/main/java/world/bentobox/aoneblock/panels/PhasesPanel.java index 2eeb76d9..0903b19f 100644 --- a/src/main/java/world/bentobox/aoneblock/panels/PhasesPanel.java +++ b/src/main/java/world/bentobox/aoneblock/panels/PhasesPanel.java @@ -453,7 +453,7 @@ private PanelItem createPhaseButton(ItemTemplateRecord template, Map.Entry this.addon.getPlugin().getVault().map(a -> a.getBalance(this.user, this.world) < requirement.getEco()).orElse(false); case PERMISSION -> this.user != null && !this.user.hasPermission(requirement.getPermission()); + + case COOLDOWN -> (requirement.getCooldown() - (System.currentTimeMillis() - is.getLastPhaseChangeTime()) / 1000) > 0; }) { return true; } diff --git a/src/main/resources/locales/cs.yml b/src/main/resources/locales/cs.yml index 8edd1981..8b01750d 100644 --- a/src/main/resources/locales/cs.yml +++ b/src/main/resources/locales/cs.yml @@ -48,7 +48,8 @@ aoneblock: 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í [jméno]!" + 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!" placeholders: infinite: Nekonečný gui: diff --git a/src/main/resources/locales/de.yml b/src/main/resources/locales/de.yml index f58c4232..ded39ead 100644 --- a/src/main/resources/locales/de.yml +++ b/src/main/resources/locales/de.yml @@ -58,6 +58,7 @@ aoneblock: Es muss [number] sein." insufficient-permission: "&c Sie können nicht weitermachen, bis Sie die [name]-Berechtigung erhalten!" + cooldown: "&c Die nächste Stufe ist in [number] Sekunden verfügbar!" placeholders: infinite: Unendlich gui: diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index cb9e8570..1d59b20e 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -50,6 +50,7 @@ aoneblock: insufficient-funds: "&c Your funds are too low to proceed! They must be [number]." insufficient-bank-balance: "&c The island bank balance is too low to proceed! It must be [number]." insufficient-permission: "&c You can proceed no further until you obtain the [name] permission!" + cooldown: "&c Next phase will be available in [number] seconds!" placeholders: infinite: Infinite gui: @@ -94,4 +95,4 @@ aoneblock: click-to-next: "&e Click &7 to view next page." click-to-change: "&e Click &7 to change." island: - starting-hologram: "&aWelcome to AOneBlock\n&eBreak This Block to Begin" \ No newline at end of file + starting-hologram: "&aWelcome to AOneBlock\n&eBreak This Block to Begin" diff --git a/src/main/resources/locales/es.yml b/src/main/resources/locales/es.yml index 5a06d1dd..70705b4c 100644 --- a/src/main/resources/locales/es.yml +++ b/src/main/resources/locales/es.yml @@ -53,6 +53,7 @@ aoneblock: para seguir! Debes tener [number]." insufficient-permission: "&c ¡No puede continuar hasta que obtenga el permiso de [name]!" + cooldown: "&c ¡La siguiente etapa estará disponible en [number] segundos!" placeholders: infinite: Infinito gui: diff --git a/src/main/resources/locales/fr.yml b/src/main/resources/locales/fr.yml index b7b6235f..f7e6cf2d 100644 --- a/src/main/resources/locales/fr.yml +++ b/src/main/resources/locales/fr.yml @@ -54,6 +54,7 @@ aoneblock: devez au moins avoir [number]. insufficient-permission: "&c Vous ne pouvez pas continuer jusqu'à ce que vous obteniez l'autorisation de [name] !" + cooldown: "&c La prochaine étape sera disponible dans [number] secondes!" placeholders: infinite: Infini gui: diff --git a/src/main/resources/locales/hr.yml b/src/main/resources/locales/hr.yml index 22cf5ad4..49b02a87 100644 --- a/src/main/resources/locales/hr.yml +++ b/src/main/resources/locales/hr.yml @@ -50,6 +50,7 @@ aoneblock: insufficient-bank-balance: "&c Stanje otočne banke je premalo za nastavak! Mora biti [number]." insufficient-permission: "&c Ne možete nastaviti dok ne dobijete dopuštenje [name]!" + cooldown: "&c Sljedeća faza bit će dostupna za [number] sekundi!" placeholders: infinite: Beskonačno gui: diff --git a/src/main/resources/locales/hu.yml b/src/main/resources/locales/hu.yml index 8ea5a438..6a692b8b 100644 --- a/src/main/resources/locales/hu.yml +++ b/src/main/resources/locales/hu.yml @@ -55,6 +55,7 @@ aoneblock: Ennek a következőnek kell lennie: [number]." insufficient-permission: "&c Nem folytathatja tovább, amíg meg nem szerzi a [name] engedélyt!" + cooldown: "&c A következő szakasz [number] másodpercen belül elérhető lesz!" placeholders: infinite: Végtelen gui: diff --git a/src/main/resources/locales/ja.yml b/src/main/resources/locales/ja.yml index 0de9eb37..f8135c54 100644 --- a/src/main/resources/locales/ja.yml +++ b/src/main/resources/locales/ja.yml @@ -47,6 +47,7 @@ aoneblock: insufficient-funds: "&c 資金が少なすぎるため続行できません。 [number] である必要があります。" insufficient-bank-balance: "&c 島の銀行残高が少なすぎるため続行できません。 [number] である必要があります。" insufficient-permission: "&c [name] の許可を取得するまで、これ以上先に進むことはできません。" + cooldown: "&c [number] 秒で次のステージへ!" placeholders: infinite: 無限 gui: diff --git a/src/main/resources/locales/pl.yml b/src/main/resources/locales/pl.yml index bbd44532..5f1e563e 100644 --- a/src/main/resources/locales/pl.yml +++ b/src/main/resources/locales/pl.yml @@ -48,6 +48,7 @@ aoneblock: aby kontynuować! Musi to być [number]. insufficient-permission: Nie możesz kontynuować, dopóki nie uzyskasz pozwolenia [name]! + cooldown: "&c Następny etap będzie dostępny za [number] sekund!" placeholders: infinite: Nieskończony gui: diff --git a/src/main/resources/locales/ru.yml b/src/main/resources/locales/ru.yml index b6ce370f..071b4a88 100644 --- a/src/main/resources/locales/ru.yml +++ b/src/main/resources/locales/ru.yml @@ -51,6 +51,7 @@ aoneblock: Должно быть [number]." insufficient-permission: "&c Вы не можете продолжать, пока не получите разрешение [name]!" + cooldown: "&c Следующий этап будет доступен через [number] секунд!" placeholders: infinite: Бесконечный gui: diff --git a/src/main/resources/locales/tr.yml b/src/main/resources/locales/tr.yml index 0adccb2b..10ef4b4c 100644 --- a/src/main/resources/locales/tr.yml +++ b/src/main/resources/locales/tr.yml @@ -43,6 +43,7 @@ aoneblock: insufficient-bank-balance: "&c Ada bankası bakiyesi devam etmek için çok düşük! [number] olmalıdır." insufficient-permission: "&c [name] iznini alana kadar devam edemezsiniz!" + cooldown: "&c Bir sonraki aşama [number] saniye içinde hazır olacak!" placeholders: infinite: Sonsuz island: diff --git a/src/main/resources/locales/vi.yml b/src/main/resources/locales/vi.yml index 62035374..09f4638b 100644 --- a/src/main/resources/locales/vi.yml +++ b/src/main/resources/locales/vi.yml @@ -44,6 +44,7 @@ aoneblock: insufficient-bank-balance: Ngân hàng đảo của bạn quá thấp để thực thi! Nó phải là [number]. insufficient-permission: Bạn không thể thực thi tiếp cho đến khi có quyền [name]! + cooldown: Giai đoạn tiếp theo sẽ có sau [number] giây! placeholders: infinite: Vô hạn island: diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index 4b16fbb9..c1767d79 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -46,6 +46,7 @@ aoneblock: insufficient-funds: "&c 余额不足, 无法执行此操作! 余额应多于 [number]." insufficient-bank-balance: "&c 岛屿银行余额不足, 无法执行此操作! 余额应多于 [number]." insufficient-permission: "&c 在获得 [name] 许可之前,您不能继续操作!" + cooldown: "&c [number] 秒后即可进入下一阶段!" placeholders: infinite: 无限 gui: diff --git a/src/main/resources/locales/zh-TW.yml b/src/main/resources/locales/zh-TW.yml index 8828d03b..4565d1ab 100644 --- a/src/main/resources/locales/zh-TW.yml +++ b/src/main/resources/locales/zh-TW.yml @@ -46,6 +46,7 @@ aoneblock: insufficient-funds: "&c 您的資金太低,無法繼續!他們必須是[數字]。" insufficient-bank-balance: "&c 島上銀行餘額太低,無法繼續!必須是[number]。" insufficient-permission: "&c 在獲得 [name] 許可之前,您不能繼續操作!" + cooldown: "&c [number] 秒後即可進入下一階段!" placeholders: infinite: 無窮 gui: diff --git a/src/main/resources/phases/0_plains.yml b/src/main/resources/phases/0_plains.yml index 588a8011..93245dbb 100644 --- a/src/main/resources/phases/0_plains.yml +++ b/src/main/resources/phases/0_plains.yml @@ -56,6 +56,7 @@ # bank-balance: 10000 # level: 10 # permission: ready.for.battle + # cooldown: 60 blocks: PODZOL: 40 From 111f2dc30b96ad6b435d6ae50c7dccd71c780960 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 24 Nov 2023 09:37:13 -0800 Subject: [PATCH 27/42] Update src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java Co-authored-by: Huynh Tien --- .../aoneblock/oneblocks/customblock/MobCustomBlock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java index 9f762808..73259493 100644 --- a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java +++ b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java @@ -19,7 +19,7 @@ /** * A custom block that spawns mob on an underlying block * - * @author HSGamer + * @author Baterka */ public class MobCustomBlock implements OneBlockCustomBlock { private final EntityType mob; From 8cf86e4a4a1f2895c996e882aee080434f844752 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 24 Nov 2023 09:53:46 -0800 Subject: [PATCH 28/42] Fixed method call for placeholder. #355 --- .../aoneblock/PlaceholdersManager.java | 476 +++++++++--------- 1 file changed, 238 insertions(+), 238 deletions(-) diff --git a/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java b/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java index 1a76a542..1517b29d 100644 --- a/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java +++ b/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java @@ -9,242 +9,242 @@ public class PlaceholdersManager { - private static final TreeMap SCALE; - static { - SCALE = new TreeMap<>(); - SCALE.put(0D, "&c╍╍╍╍╍╍╍╍"); - SCALE.put(12.5, "&a╍&c╍╍╍╍╍╍╍"); - SCALE.put(25.0, "&a╍╍&c╍╍╍╍╍╍"); - SCALE.put(37.5, "&a╍╍╍&c╍╍╍╍╍"); - SCALE.put(50D, "&a╍╍╍╍&c╍╍╍╍"); - SCALE.put(62.5, "&a╍╍╍╍╍&c╍╍╍"); - SCALE.put(75.0, "&a╍╍╍╍╍╍&c╍╍"); - SCALE.put(87.5, "&a╍╍╍╍╍╍╍&c╍"); - SCALE.put(100D, "&a╍╍╍╍╍╍╍╍"); - } - - private final AOneBlock addon; - - public PlaceholdersManager(AOneBlock addon) { - this.addon = addon; - } - - /** - * Get phase by location of user - * - * @param user - user - * @return Phase name - */ - public String getPhaseByLocation(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland).map(OneBlockIslands::getPhaseName).orElse(""); - } - - /** - * Get block count by user location - * - * @param user - user - * @return String of count - */ - public String getCountByLocation(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland).map(OneBlockIslands::getBlockNumber).map(String::valueOf).orElse(""); - } - - /** - * Get user's island phase - * - * @param user - island owner or team member - * @return phase name - */ - public String getPhase(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - return i == null ? "" : addon.getOneBlocksIsland(i).getPhaseName(); - } - - /** - * Get island block count - * - * @param user island owner or team member - * @return string of block count - */ - public String getCount(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - return i == null ? "" : String.valueOf(addon.getOneBlocksIsland(i).getBlockNumber()); - } - - /** - * Get the next phase based on user's location - * - * @param user - user - * @return next phase - */ - public String getNextPhaseByLocation(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getNextPhase).orElse(""); - } - - /** - * Get next island phase - * - * @param user island owner or team member - * @return next island phase - */ - public String getNextPhase(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - return i == null ? "" : addon.getOneBlockManager().getNextPhase(addon.getOneBlocksIsland(i)); - } - - /** - * Get how many blocks until next phase based on user's location - * - * @param user user - * @return string number of blocks - */ - public String getNextPhaseBlocksByLocation(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getNextPhaseBlocks) - .map(num -> num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num)) - .orElse(""); - } - - /** - * Get how many blocks until the next island phase - * - * @param user owner or team member - * @return string number of blocks - */ - public String getNextPhaseBlocks(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - if (i == null) { - return ""; - } - int num = addon.getOneBlockManager().getNextPhaseBlocks(addon.getOneBlocksIsland(i)); - return num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num); - } - - /** - * Get how many blocks for this phase - * - * @param user owner or team member - * @return string number of blocks - */ - public String getPhaseBlocks(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - if (i == null) { - return ""; - } - int num = addon.getOneBlockManager().getNextPhaseBlocks(addon.getOneBlocksIsland(i)); - return num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num); - } - - /** - * Get percentage done of current phase by user's location - * - * @param user - user - * @return string percentage - */ - public String getPercentDoneByLocation(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getPercentageDone) - .map(num -> Math.round(num) + "%").orElse(""); - } - - /** - * Get percentage done of user's island phase - * - * @param user owner or team member - * @return string percentage - */ - public String getPercentDone(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - if (i == null) { - return ""; - } - double num = addon.getOneBlockManager().getPercentageDone(addon.getOneBlocksIsland(i)); - return Math.round(num) + "%"; - } - - /** - * Get percentage done of phase as colored scale based on user's location - * - * @param user user - * @return colored scale - */ - public String getDoneScaleByLocation(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getPercentageDone) - .map(num -> SCALE.floorEntry(num).getValue()) - .map(s -> s.replace("╍", addon.getSettings().getPercentCompleteSymbol())).orElse(""); - } - - /** - * Get percentage done of phase as colored scale - * - * @param user owner or team member - * @return colored scale - */ - public String getDoneScale(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); - if (i == null) { - return ""; - } - double num = addon.getOneBlockManager().getPercentageDone(addon.getOneBlocksIsland(i)); - return SCALE.floorEntry(num).getValue().replace("╍", addon.getSettings().getPercentCompleteSymbol()); - } - - /** - * Get island Lifetime count - * - * @param user island owner or team member - * @return string of Lifetime count - */ - public String getLifetime(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - - Island island = this.addon.getIslands().getIsland(this.addon.getOverWorld(), user); - - return island == null ? "" : String.valueOf(this.addon.getOneBlocksIsland(island).getLifetime()); - } - - /** - * Get Lifetime count by user location - * - * @param user - user - * @return String of Lifetime - */ - public String getLifetimeByLocation(User user) { - if (user == null || user.getUniqueId() == null) - return ""; - - return this.addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) - .map(this.addon::getOneBlocksIsland).map(OneBlockIslands::getLifetime).map(String::valueOf).orElse(""); - } + private static final TreeMap SCALE; + static { + SCALE = new TreeMap<>(); + SCALE.put(0D, "&c╍╍╍╍╍╍╍╍"); + SCALE.put(12.5, "&a╍&c╍╍╍╍╍╍╍"); + SCALE.put(25.0, "&a╍╍&c╍╍╍╍╍╍"); + SCALE.put(37.5, "&a╍╍╍&c╍╍╍╍╍"); + SCALE.put(50D, "&a╍╍╍╍&c╍╍╍╍"); + SCALE.put(62.5, "&a╍╍╍╍╍&c╍╍╍"); + SCALE.put(75.0, "&a╍╍╍╍╍╍&c╍╍"); + SCALE.put(87.5, "&a╍╍╍╍╍╍╍&c╍"); + SCALE.put(100D, "&a╍╍╍╍╍╍╍╍"); + } + + private final AOneBlock addon; + + public PlaceholdersManager(AOneBlock addon) { + this.addon = addon; + } + + /** + * Get phase by location of user + * + * @param user - user + * @return Phase name + */ + public String getPhaseByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(OneBlockIslands::getPhaseName).orElse(""); + } + + /** + * Get block count by user location + * + * @param user - user + * @return String of count + */ + public String getCountByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(OneBlockIslands::getBlockNumber).map(String::valueOf).orElse(""); + } + + /** + * Get user's island phase + * + * @param user - island owner or team member + * @return phase name + */ + public String getPhase(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + return i == null ? "" : addon.getOneBlocksIsland(i).getPhaseName(); + } + + /** + * Get island block count + * + * @param user island owner or team member + * @return string of block count + */ + public String getCount(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + return i == null ? "" : String.valueOf(addon.getOneBlocksIsland(i).getBlockNumber()); + } + + /** + * Get the next phase based on user's location + * + * @param user - user + * @return next phase + */ + public String getNextPhaseByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getNextPhase).orElse(""); + } + + /** + * Get next island phase + * + * @param user island owner or team member + * @return next island phase + */ + public String getNextPhase(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + return i == null ? "" : addon.getOneBlockManager().getNextPhase(addon.getOneBlocksIsland(i)); + } + + /** + * Get how many blocks until next phase based on user's location + * + * @param user user + * @return string number of blocks + */ + public String getNextPhaseBlocksByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getNextPhaseBlocks) + .map(num -> num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num)) + .orElse(""); + } + + /** + * Get how many blocks until the next island phase + * + * @param user owner or team member + * @return string number of blocks + */ + public String getNextPhaseBlocks(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + if (i == null) { + return ""; + } + int num = addon.getOneBlockManager().getNextPhaseBlocks(addon.getOneBlocksIsland(i)); + return num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num); + } + + /** + * Get how many blocks for this phase + * + * @param user owner or team member + * @return string number of blocks + */ + public String getPhaseBlocks(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + if (i == null) { + return ""; + } + int num = addon.getOneBlockManager().getPhaseBlocks(addon.getOneBlocksIsland(i)); + return num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num); + } + + /** + * Get percentage done of current phase by user's location + * + * @param user - user + * @return string percentage + */ + public String getPercentDoneByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getPercentageDone) + .map(num -> Math.round(num) + "%").orElse(""); + } + + /** + * Get percentage done of user's island phase + * + * @param user owner or team member + * @return string percentage + */ + public String getPercentDone(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + if (i == null) { + return ""; + } + double num = addon.getOneBlockManager().getPercentageDone(addon.getOneBlocksIsland(i)); + return Math.round(num) + "%"; + } + + /** + * Get percentage done of phase as colored scale based on user's location + * + * @param user user + * @return colored scale + */ + public String getDoneScaleByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getPercentageDone) + .map(num -> SCALE.floorEntry(num).getValue()) + .map(s -> s.replace("╍", addon.getSettings().getPercentCompleteSymbol())).orElse(""); + } + + /** + * Get percentage done of phase as colored scale + * + * @param user owner or team member + * @return colored scale + */ + public String getDoneScale(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + Island i = addon.getIslands().getIsland(addon.getOverWorld(), user); + if (i == null) { + return ""; + } + double num = addon.getOneBlockManager().getPercentageDone(addon.getOneBlocksIsland(i)); + return SCALE.floorEntry(num).getValue().replace("╍", addon.getSettings().getPercentCompleteSymbol()); + } + + /** + * Get island Lifetime count + * + * @param user island owner or team member + * @return string of Lifetime count + */ + public String getLifetime(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + + Island island = this.addon.getIslands().getIsland(this.addon.getOverWorld(), user); + + return island == null ? "" : String.valueOf(this.addon.getOneBlocksIsland(island).getLifetime()); + } + + /** + * Get Lifetime count by user location + * + * @param user - user + * @return String of Lifetime + */ + public String getLifetimeByLocation(User user) { + if (user == null || user.getUniqueId() == null) + return ""; + + return this.addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation())) + .map(this.addon::getOneBlocksIsland).map(OneBlockIslands::getLifetime).map(String::valueOf).orElse(""); + } } From b6b0b53463acfefad2c8d3d2e26921a28de71fad Mon Sep 17 00:00:00 2001 From: Baterka <3463591+Baterka@users.noreply.github.com> Date: Sat, 25 Nov 2023 17:44:58 +0100 Subject: [PATCH 29/42] Fixed `cooldown` requirement (#360) * Fixed: - `Requirement - cooldown` was not working properly Refactors: - Minor refactors to `BlockListener.process` method for better readability * Clarified that requirement value is in seconds * - Moved `setNewPhase` back to `CheckPhase` class - Fixed tests - Refactored test run args as they were weirdly formatted * Removed unused imports --------- Co-authored-by: tastybento --- pom.xml | 21 +- .../aoneblock/listeners/BlockListener.java | 617 +++++++++--------- .../aoneblock/listeners/CheckPhase.java | 101 +-- .../aoneblock/listeners/HoloListener.java | 11 +- .../listeners/ItemsAdderListener.java | 3 +- .../oneblocks/OneBlockCustomBlock.java | 1 + .../oneblocks/OneBlockCustomBlockCreator.java | 11 +- .../aoneblock/oneblocks/Requirement.java | 2 + .../oneblocks/customblock/MobCustomBlock.java | 11 +- src/main/resources/phases/0_plains.yml | 2 +- .../dataobjects/OneBlockIslandsTest.java | 5 +- .../aoneblock/listeners/CheckPhaseTest.java | 318 +++++---- .../listeners/NoBlockHandlerTest.java | 39 +- 13 files changed, 584 insertions(+), 558 deletions(-) diff --git a/pom.xml b/pom.xml index e52dca4a..d29310d8 100644 --- a/pom.xml +++ b/pom.xml @@ -292,26 +292,19 @@ --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED - --add-opens - java.base/java.util.stream=ALL-UNNAMED + --add-opens java.base/java.util.stream=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED - --add-opens - java.base/java.util.regex=ALL-UNNAMED - --add-opens - java.base/java.nio.channels.spi=ALL-UNNAMED + --add-opens java.base/java.util.regex=ALL-UNNAMED + --add-opens java.base/java.nio.channels.spi=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED - --add-opens - java.base/java.util.concurrent=ALL-UNNAMED + --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/sun.nio.fs=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/java.nio.file=ALL-UNNAMED - --add-opens - java.base/java.nio.charset=ALL-UNNAMED - --add-opens - java.base/java.lang.reflect=ALL-UNNAMED - --add-opens - java.logging/java.util.logging=ALL-UNNAMED + --add-opens java.base/java.nio.charset=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED + --add-opens java.logging/java.util.logging=ALL-UNNAMED --add-opens java.base/java.lang.ref=ALL-UNNAMED --add-opens java.base/java.util.jar=ALL-UNNAMED --add-opens java.base/java.util.zip=ALL-UNNAMED diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java index fc3ca583..d6234ecd 100644 --- a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java +++ b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java @@ -104,90 +104,90 @@ public class BlockListener implements Listener { private final Random random = new Random(); - /** * @param addon - OneBlock */ public BlockListener(@NonNull AOneBlock addon) { - this.addon = addon; - handler = new Database<>(addon, OneBlockIslands.class); - cache = new HashMap<>(); - oneBlocksManager = addon.getOneBlockManager(); - check = new CheckPhase(addon, this); - warningSounder = new WarningSounder(addon); + this.addon = addon; + handler = new Database<>(addon, OneBlockIslands.class); + cache = new HashMap<>(); + oneBlocksManager = addon.getOneBlockManager(); + check = new CheckPhase(addon, this); + warningSounder = new WarningSounder(addon); } /** * Save the island cache */ public void saveCache() { - cache.values().forEach(handler::saveObjectAsync); + cache.values().forEach(handler::saveObjectAsync); } - // --------------------------------------------------------------------- // Section: Listeners // --------------------------------------------------------------------- - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIsland(IslandCreatedEvent e) { - if (addon.inWorld(e.getIsland().getWorld())) { - setUp(e.getIsland()); - } + if (addon.inWorld(e.getIsland().getWorld())) { + setUp(e.getIsland()); + } } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIsland(IslandResettedEvent e) { - if (addon.inWorld(e.getIsland().getWorld())) { - setUp(e.getIsland()); - } + if (addon.inWorld(e.getIsland().getWorld())) { + setUp(e.getIsland()); + } } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onDeletedIsland(IslandDeleteEvent e) { - if (addon.inWorld(e.getIsland().getWorld())) { - cache.remove(e.getIsland().getUniqueId()); - handler.deleteID(e.getIsland().getUniqueId()); - } + if (addon.inWorld(e.getIsland().getWorld())) { + cache.remove(e.getIsland().getUniqueId()); + handler.deleteID(e.getIsland().getUniqueId()); + } } /** * Prevents liquids flowing into magic block + * * @param e BlockFromToEvent */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onBlockFromTo(final BlockFromToEvent e) { - if (!addon.inWorld(e.getBlock().getWorld())) { - return; - } - Location l = e.getToBlock().getLocation(); - // Cannot flow to center block - e.setCancelled(addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())).isPresent()); + if (!addon.inWorld(e.getBlock().getWorld())) { + return; + } + Location l = e.getToBlock().getLocation(); + // Cannot flow to center block + e.setCancelled(addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())).isPresent()); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onBlockBreak(final BlockBreakEvent e) { - if (!addon.inWorld(e.getBlock().getWorld())) { - return; - } - Location l = e.getBlock().getLocation(); - addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())).ifPresent(i -> process(e, i, e.getPlayer(), e.getPlayer().getWorld())); + if (!addon.inWorld(e.getBlock().getWorld())) { + return; + } + Location l = e.getBlock().getLocation(); + addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())) + .ifPresent(i -> process(e, i, e.getPlayer(), e.getPlayer().getWorld())); } /** - * Handles JetsMinions. These are special armor stands. Requires Minions 6.9.3 or later + * Handles JetsMinions. These are special armor stands. Requires Minions 6.9.3 + * or later * * @param e - event */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onBlockBreakByMinion(final EntityInteractEvent e) { - if (!addon.inWorld(e.getBlock().getWorld()) || !e.getEntityType().equals(EntityType.ARMOR_STAND)) { - return; - } - Location l = e.getBlock().getLocation(); - addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())).ifPresent(i -> process(e, i, null, e.getBlock().getWorld())); + if (!addon.inWorld(e.getBlock().getWorld()) || !e.getEntityType().equals(EntityType.ARMOR_STAND)) { + return; + } + Location l = e.getBlock().getLocation(); + addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())) + .ifPresent(i -> process(e, i, null, e.getBlock().getWorld())); } /** @@ -197,70 +197,63 @@ public void onBlockBreakByMinion(final EntityInteractEvent e) { */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onBlockBreak(final PlayerBucketFillEvent e) { - if (addon.inWorld(e.getBlock().getWorld())) { - Location l = e.getBlock().getLocation(); - addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())).ifPresent(i -> process(e, i, e.getPlayer(), e.getPlayer().getWorld())); - } + if (addon.inWorld(e.getBlock().getWorld())) { + Location l = e.getBlock().getLocation(); + addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())) + .ifPresent(i -> process(e, i, e.getPlayer(), e.getPlayer().getWorld())); + } } - /** * Drop items at the top of the block. + * * @param event EntitySpawnEvent object. */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onItemStackSpawn(EntitySpawnEvent event) - { - if (!this.addon.getSettings().isDropOnTop()) - { - // Do nothing as item spawning is not interested in this case. - return; - } - - if (!EntityType.DROPPED_ITEM.equals(event.getEntityType())) - { - // We are interested only in dropped item entities. - return; - } - - if (!this.addon.inWorld(event.getLocation().getWorld())) - { - // Not correct world - return; - } - - Entity entity = event.getEntity(); - Location location = event.getLocation(); - - Optional optionalIsland = this.addon.getIslands(). - getIslandAt(location). - filter(island -> location.getBlock().getLocation().equals(island.getCenter())); - - if (optionalIsland.isPresent()) - { - // Teleport entity to the top of magic block. - entity.teleport(optionalIsland.get().getCenter().add(0.5, 1, 0.5)); - entity.setVelocity(new Vector(0, 0, 0)); - } + public void onItemStackSpawn(EntitySpawnEvent event) { + if (!this.addon.getSettings().isDropOnTop()) { + // Do nothing as item spawning is not interested in this case. + return; + } + + if (!EntityType.DROPPED_ITEM.equals(event.getEntityType())) { + // We are interested only in dropped item entities. + return; + } + + if (!this.addon.inWorld(event.getLocation().getWorld())) { + // Not correct world + return; + } + + Entity entity = event.getEntity(); + Location location = event.getLocation(); + + Optional optionalIsland = this.addon.getIslands().getIslandAt(location) + .filter(island -> location.getBlock().getLocation().equals(island.getCenter())); + + if (optionalIsland.isPresent()) { + // Teleport entity to the top of magic block. + entity.teleport(optionalIsland.get().getCenter().add(0.5, 1, 0.5)); + entity.setVelocity(new Vector(0, 0, 0)); + } } - // --------------------------------------------------------------------- // Section: Processing methods // --------------------------------------------------------------------- - private void setUp(@NonNull Island island) { - // Set the bedrock to the initial block - Util.getChunkAtAsync(Objects.requireNonNull(island.getCenter())).thenRun(() -> island.getCenter().getBlock().setType(Material.GRASS_BLOCK)); - // Create a database entry - OneBlockIslands is = new OneBlockIslands(island.getUniqueId()); - cache.put(island.getUniqueId(), is); - handler.saveObjectAsync(is); - addon.getHoloListener().setUp(island, is, true); + // Set the bedrock to the initial block + Util.getChunkAtAsync(Objects.requireNonNull(island.getCenter())) + .thenRun(() -> island.getCenter().getBlock().setType(Material.GRASS_BLOCK)); + // Create a database entry + OneBlockIslands is = new OneBlockIslands(island.getUniqueId()); + cache.put(island.getUniqueId(), is); + handler.saveObjectAsync(is); + addon.getHoloListener().setUp(island, is, true); } - /** * Main block processing method * @@ -270,219 +263,251 @@ private void setUp(@NonNull Island island) { * @param world - world where the block is being broken */ private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player player, @NonNull World world) { - // Get island from cache or load it - OneBlockIslands is = getIsland(i); - // Get the phase for this island - OneBlockPhase phase = oneBlocksManager.getPhase(is.getBlockNumber()); - // Store the original phase in case it changes. - String originalPhase = is.getPhaseName(); - // Check for a goto - if (Objects.requireNonNull(phase).getGotoBlock() != null) { - handleGoto(is, phase); - } - // Check for new phase and run commands if required - boolean newPhase = check.checkPhase(player, i, is, Objects.requireNonNull(phase)); - if (!newPhase && is.getBlockNumber() % SAVE_EVERY == 0) { - // Save island data every MAX_LOOK_AHEAD blocks. - saveIsland(i); - } - // Check if requirements met - if (check.phaseRequirementsFail(player, i, is, phase, world)) { - e.setCancelled(true); - return; - } - if (newPhase) { - is.clearQueue(); - is.setLastPhaseChangeTime(System.currentTimeMillis()); - } - // Get the block number in this phase - int materialBlocksInQueue = (int) is.getQueue().stream().filter(obo -> obo.isMaterial() || obo.isCustomBlock()).count(); - int blockNumber = is.getBlockNumber() - (phase.getBlockNumberValue() - 1) + materialBlocksInQueue; - // Get the block that is being broken - Block block = Objects.requireNonNull(i.getCenter()).toVector().toLocation(world).getBlock(); - // Fill a 5 block queue - if (is.getQueue().isEmpty() || newPhase) { - // Add initial 5 blocks - for (int j = 0; j < MAX_LOOK_AHEAD; j++) { - is.add(phase.getNextBlock(addon, blockNumber++)); - } - } - // Manage Holograms - addon.getHoloListener().process(i, is, phase); - // Play warning sound for upcoming mobs - if (addon.getSettings().getMobWarning() > 0) { - warningSounder.play(is, block); - } - // Get the next block - OneBlockObject nextBlock = (newPhase && phase.getFirstBlock() != null) ? phase.getFirstBlock() : is.pollAndAdd(phase.getNextBlock(addon, blockNumber++)); - // Check if this is a new Phase - if (newPhase) { - // Set the biome for the block and one block above it - setBiome(block, phase.getPhaseBiome()); - // Fire new phase event - Bukkit.getPluginManager().callEvent(new MagicBlockPhaseEvent(i, player == null ? null : player.getUniqueId(), block, phase.getPhaseName(), originalPhase, is.getBlockNumber())); - } - // Entity - if (nextBlock.isEntity()) { - if (!(e instanceof EntitySpawnEvent)) e.setCancelled(true); - // Entity spawns do not increment the block number or break the block - spawnEntity(nextBlock, block); - // Fire event - Bukkit.getPluginManager().callEvent(new MagicBlockEntityEvent(i, player == null ? null : player.getUniqueId(), block, nextBlock.getEntityType())); - return; - } - // Break the block - if (e instanceof BlockBreakEvent) { - this.breakBlock(player, block, nextBlock, i); - } else if (e instanceof PlayerBucketFillEvent) { - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> spawnBlock(nextBlock, block)); - // Fire event - ItemStack tool = Objects.requireNonNull(player).getInventory().getItemInMainHand(); - Bukkit.getPluginManager().callEvent(new MagicBlockEvent(i, player.getUniqueId(), tool, block, nextBlock.getMaterial())); - } else if (e instanceof EntitySpawnEvent) { - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> spawnBlock(nextBlock, block)); - } else if (e instanceof EntityInteractEvent) { - // Minion breaking block - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> spawnBlock(nextBlock, block)); - // Fire event - Bukkit.getPluginManager().callEvent(new MagicBlockEvent(i, null, null, block, nextBlock.getMaterial())); - } - // Increment the block number - is.incrementBlockNumber(); + // Get the block that is being broken + Block block = Objects.requireNonNull(i.getCenter()).toVector().toLocation(world).getBlock(); + + // Get oneblock island + OneBlockIslands is = getIsland(i); + + // Get the phase for current block number + OneBlockPhase phase = oneBlocksManager.getPhase(is.getBlockNumber()); + + // Save previous processing phase name + String prevPhaseName = is.getPhaseName(); + + // Check for a goto + if (Objects.requireNonNull(phase).getGotoBlock() != null) { + handleGoto(is, phase); + } + + // Get current phase name + String currPhaseName = phase.getPhaseName() == null ? "" : phase.getPhaseName(); + + // Get the phase for next block number + OneBlockPhase nextPhase = oneBlocksManager.getPhase(is.getBlockNumber() + 1); + + // Get next phase name + String nextPhaseName = nextPhase == null || nextPhase.getPhaseName() == null ? "" : nextPhase.getPhaseName(); + + // If next phase is new, log break time of the last block of this phase + if (!currPhaseName.equalsIgnoreCase(nextPhaseName)) { + is.setLastPhaseChangeTime(System.currentTimeMillis()); + } + + boolean isCurrPhaseNew = !is.getPhaseName().equalsIgnoreCase(currPhaseName); + + if (isCurrPhaseNew) { + // Check if requirements for new phase are met + if (check.phaseRequirementsFail(player, i, is, phase, world)) { + e.setCancelled(true); + return; + } + + check.setNewPhase(player, i, is, phase); + is.clearQueue(); + + // Set the biome for the block and one block above it + setBiome(block, phase.getPhaseBiome()); + + // Fire new phase event + Bukkit.getPluginManager() + .callEvent(new MagicBlockPhaseEvent(i, player == null ? null : player.getUniqueId(), block, + phase.getPhaseName(), prevPhaseName, is.getBlockNumber())); + } + + if (!isCurrPhaseNew && is.getBlockNumber() % SAVE_EVERY == 0) { + // Save island data every MAX_LOOK_AHEAD blocks. + saveIsland(i); + } + + // Get the block number in this phase + int materialBlocksInQueue = (int) is.getQueue().stream().filter(obo -> obo.isMaterial() || obo.isCustomBlock()) + .count(); + int blockNumber = is.getBlockNumber() - (phase.getBlockNumberValue() - 1) + materialBlocksInQueue; + + // Fill a 5 block queue + if (is.getQueue().isEmpty() || isCurrPhaseNew) { + // Add initial 5 blocks + for (int j = 0; j < MAX_LOOK_AHEAD; j++) { + is.add(phase.getNextBlock(addon, blockNumber++)); + } + } + + // Manage Holograms + addon.getHoloListener().process(i, is, phase); + + // Play warning sound for upcoming mobs + if (addon.getSettings().getMobWarning() > 0) { + warningSounder.play(is, block); + } + + // Get the next block + OneBlockObject nextBlock = (isCurrPhaseNew && phase.getFirstBlock() != null) ? phase.getFirstBlock() + : is.pollAndAdd(phase.getNextBlock(addon, blockNumber++)); + + // Entity + if (nextBlock.isEntity()) { + if (!(e instanceof EntitySpawnEvent)) + e.setCancelled(true); + // Entity spawns do not increment the block number or break the block + spawnEntity(nextBlock, block); + // Fire event + Bukkit.getPluginManager().callEvent(new MagicBlockEntityEvent(i, + player == null ? null : player.getUniqueId(), block, nextBlock.getEntityType())); + return; + } + + // Break the block + if (e instanceof BlockBreakEvent) { + this.breakBlock(player, block, nextBlock, i); + } else if (e instanceof PlayerBucketFillEvent) { + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> spawnBlock(nextBlock, block)); + // Fire event + ItemStack tool = Objects.requireNonNull(player).getInventory().getItemInMainHand(); + Bukkit.getPluginManager() + .callEvent(new MagicBlockEvent(i, player.getUniqueId(), tool, block, nextBlock.getMaterial())); + } else if (e instanceof EntitySpawnEvent) { + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> spawnBlock(nextBlock, block)); + } else if (e instanceof EntityInteractEvent) { + // Minion breaking block + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> spawnBlock(nextBlock, block)); + // Fire event + Bukkit.getPluginManager().callEvent(new MagicBlockEvent(i, null, null, block, nextBlock.getMaterial())); + } + + // Increment the block number + is.incrementBlockNumber(); } private void handleGoto(OneBlockIslands is, OneBlockPhase phase) { - int gotoBlock = phase.getGotoBlock(); - phase = oneBlocksManager.getPhase(gotoBlock); - // Store lifetime - is.setLifetime(is.getLifetime() + gotoBlock); - // Set current block - is.setBlockNumber(gotoBlock); + int gotoBlock = phase.getGotoBlock(); + phase = oneBlocksManager.getPhase(gotoBlock); + // Store lifetime + is.setLifetime(is.getLifetime() + gotoBlock); + // Set current block + is.setBlockNumber(gotoBlock); } private void setBiome(@NonNull Block block, @Nullable Biome biome) { - if (biome == null) { - return; - } - for (int x = -4; x <= 4; x++) { - for (int z = -4; z <= 4; z++) { - for (int y = -4; y <= 4; y++) { - block.getWorld().setBiome(block.getX() + x, block.getY() + y, block.getZ() + z, biome); - } - } - } + if (biome == null) { + return; + } + for (int x = -4; x <= 4; x++) { + for (int z = -4; z <= 4; z++) { + for (int y = -4; y <= 4; y++) { + block.getWorld().setBiome(block.getX() + x, block.getY() + y, block.getZ() + z, biome); + } + } + } } - /** - * This method is called when block is removed, and next must be spawned. - * It also teleports player above the magic block, to avoid falling in void. - * @param player Player who breaks the block. - * @param block Block that was broken. + * This method is called when block is removed, and next must be spawned. It + * also teleports player above the magic block, to avoid falling in void. + * + * @param player Player who breaks the block. + * @param block Block that was broken. * @param nextBlock Next Block that will be summoned. - * @param island Island where player is located. + * @param island Island where player is located. */ - private void breakBlock(@Nullable Player player, Block block, @NonNull OneBlockObject nextBlock, @NonNull Island island) - { - ItemStack tool = Objects.requireNonNull(player).getInventory().getItemInMainHand(); - - // Break normally and lift the player up so they don't fall - Bukkit.getScheduler().runTask(addon.getPlugin(), () -> this.spawnBlock(nextBlock, block)); - - if (player.getLocation().getBlock().equals(block)) - { - double delta = 1 - (player.getLocation().getY() - block.getY()); - player.teleport(player.getLocation().add(new Vector(0, delta, 0))); - player.setVelocity(new Vector(0, 0, 0)); - } - else if (player.getLocation().getBlock().equals(block.getRelative(BlockFace.UP))) - { - player.teleport(player.getLocation()); - player.setVelocity(new Vector(0, 0, 0)); - } - - // Fire event - Bukkit.getPluginManager().callEvent(new MagicBlockEvent(island, player.getUniqueId(), tool, block, nextBlock.getMaterial())); + private void breakBlock(@Nullable Player player, Block block, @NonNull OneBlockObject nextBlock, + @NonNull Island island) { + ItemStack tool = Objects.requireNonNull(player).getInventory().getItemInMainHand(); + + // Break normally and lift the player up so they don't fall + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> this.spawnBlock(nextBlock, block)); + + if (player.getLocation().getBlock().equals(block)) { + double delta = 1 - (player.getLocation().getY() - block.getY()); + player.teleport(player.getLocation().add(new Vector(0, delta, 0))); + player.setVelocity(new Vector(0, 0, 0)); + } else if (player.getLocation().getBlock().equals(block.getRelative(BlockFace.UP))) { + player.teleport(player.getLocation()); + player.setVelocity(new Vector(0, 0, 0)); + } + + // Fire event + Bukkit.getPluginManager() + .callEvent(new MagicBlockEvent(island, player.getUniqueId(), tool, block, nextBlock.getMaterial())); } - private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block) { - if (nextBlock.isCustomBlock()) { - nextBlock.getCustomBlock().execute(addon, block); - } else if (nextBlock.isItemsAdderBlock()) { - //Get Custom Block from ItemsAdder and place it - CustomBlock cBlock = CustomBlock.getInstance(nextBlock.getItemsAdderBlock()); - if (cBlock != null) { - block.getLocation().getBlock().setType(Material.AIR); - cBlock.place(block.getLocation()); - } - } else { - @NonNull - Material type = nextBlock.getMaterial(); - // Place new block with no physics - block.setType(type, false); - // Fill the chest - if (type.equals(Material.CHEST) && nextBlock.getChest() != null) { - fillChest(nextBlock, block); - return; - } else if (Tag.LEAVES.isTagged(type)) { - Leaves leaves = (Leaves) block.getState().getBlockData(); - leaves.setPersistent(true); - block.setBlockData(leaves); - } else if (block.getState() instanceof BrushableBlock bb) { - LootTable lt = switch(bb.getBlock().getBiome()) { - default -> { - if (random.nextDouble() < 0.8) { - yield LootTables.TRAIL_RUINS_ARCHAEOLOGY_COMMON.getLootTable(); - } else { - // 20% rare - yield LootTables.TRAIL_RUINS_ARCHAEOLOGY_RARE.getLootTable(); - } - } - case DESERT -> LootTables.DESERT_PYRAMID_ARCHAEOLOGY.getLootTable(); - case FROZEN_OCEAN -> LootTables.OCEAN_RUIN_COLD_ARCHAEOLOGY.getLootTable(); - case OCEAN -> LootTables.OCEAN_RUIN_COLD_ARCHAEOLOGY.getLootTable(); - case WARM_OCEAN -> LootTables.OCEAN_RUIN_WARM_ARCHAEOLOGY.getLootTable(); - }; - bb.setLootTable(lt); - bb.update(); - } - } + if (nextBlock.isCustomBlock()) { + nextBlock.getCustomBlock().execute(addon, block); + } else if (nextBlock.isItemsAdderBlock()) { + // Get Custom Block from ItemsAdder and place it + CustomBlock cBlock = CustomBlock.getInstance(nextBlock.getItemsAdderBlock()); + if (cBlock != null) { + block.getLocation().getBlock().setType(Material.AIR); + cBlock.place(block.getLocation()); + } + } else { + @NonNull + Material type = nextBlock.getMaterial(); + // Place new block with no physics + block.setType(type, false); + // Fill the chest + if (type.equals(Material.CHEST) && nextBlock.getChest() != null) { + fillChest(nextBlock, block); + return; + } else if (Tag.LEAVES.isTagged(type)) { + Leaves leaves = (Leaves) block.getState().getBlockData(); + leaves.setPersistent(true); + block.setBlockData(leaves); + } else if (block.getState() instanceof BrushableBlock bb) { + LootTable lt = switch (bb.getBlock().getBiome()) { + default -> { + if (random.nextDouble() < 0.8) { + yield LootTables.TRAIL_RUINS_ARCHAEOLOGY_COMMON.getLootTable(); + } else { + // 20% rare + yield LootTables.TRAIL_RUINS_ARCHAEOLOGY_RARE.getLootTable(); + } + } + case DESERT -> LootTables.DESERT_PYRAMID_ARCHAEOLOGY.getLootTable(); + case FROZEN_OCEAN -> LootTables.OCEAN_RUIN_COLD_ARCHAEOLOGY.getLootTable(); + case OCEAN -> LootTables.OCEAN_RUIN_COLD_ARCHAEOLOGY.getLootTable(); + case WARM_OCEAN -> LootTables.OCEAN_RUIN_WARM_ARCHAEOLOGY.getLootTable(); + }; + bb.setLootTable(lt); + bb.update(); + } + } } private void spawnEntity(@NonNull OneBlockObject nextBlock, @NonNull Block block) { - if (block.isEmpty()) block.setType(Material.STONE); - Location spawnLoc = block.getLocation().add(new Vector(0.5D, 1D, 0.5D)); - Entity entity = block.getWorld().spawnEntity(spawnLoc, nextBlock.getEntityType()); - // Make space for entity - this will blot out blocks - if (addon.getSettings().isClearBlocks()) { - new MakeSpace(addon).makeSpace(entity, spawnLoc); - } - block.getWorld().playSound(block.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1F, 2F); + if (block.isEmpty()) + block.setType(Material.STONE); + Location spawnLoc = block.getLocation().add(new Vector(0.5D, 1D, 0.5D)); + Entity entity = block.getWorld().spawnEntity(spawnLoc, nextBlock.getEntityType()); + // Make space for entity - this will blot out blocks + if (addon.getSettings().isClearBlocks()) { + new MakeSpace(addon).makeSpace(entity, spawnLoc); + } + block.getWorld().playSound(block.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1F, 2F); } - - private void fillChest(@NonNull OneBlockObject nextBlock, @NonNull Block block) { - Chest chest = (Chest) block.getState(); - nextBlock.getChest().forEach(chest.getBlockInventory()::setItem); - Color color = Color.fromBGR(0, 255, 255); // yellow - switch (nextBlock.getRarity()) { - case EPIC: - color = Color.fromBGR(255, 0, 255); // magenta - break; - case RARE: - color = Color.fromBGR(255, 255, 255); // cyan - break; - case UNCOMMON: - // Yellow - break; - default: - // No sparkles for regular chests - return; - } - block.getWorld().spawnParticle(Particle.REDSTONE, block.getLocation().add(new Vector(0.5, 1.0, 0.5)), 50, 0.5, 0, 0.5, 1, new Particle.DustOptions(color, 1)); + Chest chest = (Chest) block.getState(); + nextBlock.getChest().forEach(chest.getBlockInventory()::setItem); + Color color = Color.fromBGR(0, 255, 255); // yellow + switch (nextBlock.getRarity()) { + case EPIC: + color = Color.fromBGR(255, 0, 255); // magenta + break; + case RARE: + color = Color.fromBGR(255, 255, 255); // cyan + break; + case UNCOMMON: + // Yellow + break; + default: + // No sparkles for regular chests + return; + } + block.getWorld().spawnParticle(Particle.REDSTONE, block.getLocation().add(new Vector(0.5, 1.0, 0.5)), 50, 0.5, + 0, 0.5, 1, new Particle.DustOptions(color, 1)); } /** @@ -493,47 +518,49 @@ private void fillChest(@NonNull OneBlockObject nextBlock, @NonNull Block block) */ @NonNull public OneBlockIslands getIsland(@NonNull Island i) { - return cache.containsKey(i.getUniqueId()) ? cache.get(i.getUniqueId()) : loadIsland(i.getUniqueId()); + return cache.containsKey(i.getUniqueId()) ? cache.get(i.getUniqueId()) : loadIsland(i.getUniqueId()); } /** * Get all the OneBlockIslands from the Database + * * @return list of oneblock islands */ public List getAllIslands() { - return handler.loadObjects(); + return handler.loadObjects(); } @NonNull private OneBlockIslands loadIsland(@NonNull String uniqueId) { - if (handler.objectExists(uniqueId)) { - OneBlockIslands island = handler.loadObject(uniqueId); - if (island != null) { - // Add to cache - cache.put(island.getUniqueId(), island); - return island; - } - } - return cache.computeIfAbsent(uniqueId, OneBlockIslands::new); + if (handler.objectExists(uniqueId)) { + OneBlockIslands island = handler.loadObject(uniqueId); + if (island != null) { + // Add to cache + cache.put(island.getUniqueId(), island); + return island; + } + } + return cache.computeIfAbsent(uniqueId, OneBlockIslands::new); } /** * @return the oneBlocksManager */ public OneBlocksManager getOneBlocksManager() { - return oneBlocksManager; + return oneBlocksManager; } /** * Saves the island progress to the database async * * @param island - island - * @return CompletableFuture - true if saved or not in cache, false if save failed + * @return CompletableFuture - true if saved or not in cache, false if save + * failed */ public CompletableFuture saveIsland(@NonNull Island island) { - if (cache.containsKey(island.getUniqueId())) { - return handler.saveObjectAsync(cache.get(island.getUniqueId())); - } - return CompletableFuture.completedFuture(true); + if (cache.containsKey(island.getUniqueId())) { + return handler.saveObjectAsync(cache.get(island.getUniqueId())); + } + return CompletableFuture.completedFuture(true); } } diff --git a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java index 5a72bb6a..337c75e3 100644 --- a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java +++ b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java @@ -43,6 +43,57 @@ public CheckPhase(AOneBlock addon, BlockListener blockListener) { } + /** + * Runs end phase commands, sets new phase and runs new phase commands + * + * @param player - player + * @param i - island + * @param is - OneBlockIslands object + * @param phase - current phase + */ + void setNewPhase( + @Nullable Player player, + @NonNull Island i, + @NonNull OneBlockIslands is, + @NonNull OneBlockPhase phase + ) { + // Handle NPCs + User user; + if (player == null || player.hasMetadata("NPC")) { + // Default to the owner + user = addon.getPlayers().getUser(i.getOwner()); + } else { + user = User.getInstance(player); + } + + String newPhaseName = phase.getPhaseName(); + + // Run previous phase end commands + oneBlocksManager.getPhase(is.getPhaseName()).ifPresent(oldPhase -> { + String oldPhaseName = oldPhase.getPhaseName() == null ? "" : oldPhase.getPhaseName(); + Util.runCommands(user, + replacePlaceholders(player, oldPhaseName, phase.getBlockNumber(), i, oldPhase.getEndCommands()), + "Commands run for end of " + oldPhaseName); + // If first time + if (is.getBlockNumber() >= is.getLifetime()) { + Util.runCommands(user, + replacePlaceholders(player, oldPhaseName, phase.getBlockNumber(), i, oldPhase.getFirstTimeEndCommands()), + "Commands run for first time completing " + oldPhaseName); + } + }); + // Set the phase name + is.setPhaseName(newPhaseName); + if (user.isPlayer() && user.isOnline() && addon.inWorld(user.getWorld())) { + user.getPlayer().sendTitle(newPhaseName, null, -1, -1, -1); + } + // Run phase start commands + Util.runCommands(user, + replacePlaceholders(player, newPhaseName, phase.getBlockNumber(), i, phase.getStartCommands()), + "Commands run for start of " + newPhaseName); + + blockListener.saveIsland(i); + } + /** * Checks whether the player can proceed to the next phase * @@ -102,56 +153,6 @@ protected boolean phaseRequirementsFail(@Nullable Player player, @NonNull Island return blocked; } - /** - * Check whether this phase is done or not. - * - * @param player - player - * @param i - island - * @param is - OneBlockIslands object - * @param phase - current phase name - * @return true if this is a new phase, false if not - */ - protected boolean checkPhase(@Nullable Player player, @NonNull Island i, @NonNull OneBlockIslands is, @NonNull OneBlockPhase phase) { - // Handle NPCs - User user; - if (player == null || player.hasMetadata("NPC")) { - // Default to the owner - user = addon.getPlayers().getUser(i.getOwner()); - } else { - user = User.getInstance(player); - } - - String phaseName = phase.getPhaseName() == null ? "" : phase.getPhaseName(); - if (!is.getPhaseName().equalsIgnoreCase(phaseName)) { - // Run previous phase end commands - oneBlocksManager.getPhase(is.getPhaseName()).ifPresent(oldPhase -> { - String oldPhaseName = oldPhase.getPhaseName() == null ? "" : oldPhase.getPhaseName(); - Util.runCommands(user, - replacePlaceholders(player, oldPhaseName, phase.getBlockNumber(), i, oldPhase.getEndCommands()), - "Commands run for end of " + oldPhaseName); - // If first time - if (is.getBlockNumber() >= is.getLifetime()) { - Util.runCommands(user, - replacePlaceholders(player, oldPhaseName, phase.getBlockNumber(), i, oldPhase.getFirstTimeEndCommands()), - "Commands run for first time completing " + oldPhaseName); - } - }); - // Set the phase name - is.setPhaseName(phaseName); - if (user.isPlayer() && user.isOnline() && addon.inWorld(user.getWorld())) { - user.getPlayer().sendTitle(phaseName, null, -1, -1, -1); - } - // Run phase start commands - Util.runCommands(user, - replacePlaceholders(player, phaseName, phase.getBlockNumber(), i, phase.getStartCommands()), - "Commands run for start of " + phaseName); - - blockListener.saveIsland(i); - return true; - } - return false; - } - /** * Replaces placeholders in commands. *
diff --git a/src/main/java/world/bentobox/aoneblock/listeners/HoloListener.java b/src/main/java/world/bentobox/aoneblock/listeners/HoloListener.java
index 8a3efb22..21f3dc78 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/HoloListener.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/HoloListener.java
@@ -1,5 +1,10 @@
 package world.bentobox.aoneblock.listeners;
 
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+
 import org.bukkit.Bukkit;
 import org.bukkit.Location;
 import org.bukkit.World;
@@ -9,6 +14,7 @@
 import org.bukkit.event.EventPriority;
 import org.bukkit.event.Listener;
 import org.eclipse.jdt.annotation.NonNull;
+
 import world.bentobox.aoneblock.AOneBlock;
 import world.bentobox.aoneblock.dataobjects.OneBlockIslands;
 import world.bentobox.aoneblock.oneblocks.OneBlockPhase;
@@ -17,11 +23,6 @@
 import world.bentobox.bentobox.database.objects.Island;
 import world.bentobox.bentobox.util.Util;
 
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.UUID;
-
 /**
  * Handles Holographic elements
  *
diff --git a/src/main/java/world/bentobox/aoneblock/listeners/ItemsAdderListener.java b/src/main/java/world/bentobox/aoneblock/listeners/ItemsAdderListener.java
index a6948c77..bf4c25bb 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/ItemsAdderListener.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/ItemsAdderListener.java
@@ -1,8 +1,9 @@
 package world.bentobox.aoneblock.listeners;
 
-import dev.lone.itemsadder.api.Events.ItemsAdderLoadDataEvent;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
+
+import dev.lone.itemsadder.api.Events.ItemsAdderLoadDataEvent;
 import world.bentobox.aoneblock.AOneBlock;
 
 /**
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlock.java
index 4c30b8d8..458eb47a 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlock.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlock.java
@@ -1,6 +1,7 @@
 package world.bentobox.aoneblock.oneblocks;
 
 import org.bukkit.block.Block;
+
 import world.bentobox.aoneblock.AOneBlock;
 
 /**
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlockCreator.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlockCreator.java
index 91d8c040..d46b507c 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlockCreator.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockCustomBlockCreator.java
@@ -1,11 +1,16 @@
 package world.bentobox.aoneblock.oneblocks;
 
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+
 import world.bentobox.aoneblock.oneblocks.customblock.BlockDataCustomBlock;
 import world.bentobox.aoneblock.oneblocks.customblock.MobCustomBlock;
 
-import java.util.*;
-import java.util.function.Function;
-
 /**
  * A creator for {@link OneBlockCustomBlock}
  *
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java b/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java
index cc4f62e0..7a56ce37 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java
@@ -1,5 +1,7 @@
 package world.bentobox.aoneblock.oneblocks;
 
+import world.bentobox.aoneblock.oneblocks.Requirement.ReqType;
+
 /**
  * Requirement for finishing a phase
  * @author tastybento
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java
index 73259493..6e95a966 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java
@@ -1,6 +1,10 @@
 package world.bentobox.aoneblock.oneblocks.customblock;
 
-import com.google.common.base.Enums;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
 import org.bukkit.Location;
 import org.bukkit.Material;
 import org.bukkit.Sound;
@@ -9,13 +13,14 @@
 import org.bukkit.entity.EntityType;
 import org.bukkit.util.Vector;
 import org.eclipse.jdt.annotation.NonNull;
+
+import com.google.common.base.Enums;
+
 import world.bentobox.aoneblock.AOneBlock;
 import world.bentobox.aoneblock.listeners.MakeSpace;
 import world.bentobox.aoneblock.oneblocks.OneBlockCustomBlock;
 import world.bentobox.bentobox.BentoBox;
 
-import java.util.*;
-
 /**
  * A custom block that spawns mob on an underlying block
  *
diff --git a/src/main/resources/phases/0_plains.yml b/src/main/resources/phases/0_plains.yml
index 93245dbb..1d184a41 100644
--- a/src/main/resources/phases/0_plains.yml
+++ b/src/main/resources/phases/0_plains.yml
@@ -56,7 +56,7 @@
   #   bank-balance: 10000
   #   level: 10
   #   permission: ready.for.battle
-  #   cooldown: 60
+  #   cooldown: 60 # seconds
   
   blocks:
     PODZOL: 40
diff --git a/src/test/java/world/bentobox/aoneblock/dataobjects/OneBlockIslandsTest.java b/src/test/java/world/bentobox/aoneblock/dataobjects/OneBlockIslandsTest.java
index 772d8ef9..714e915f 100644
--- a/src/test/java/world/bentobox/aoneblock/dataobjects/OneBlockIslandsTest.java
+++ b/src/test/java/world/bentobox/aoneblock/dataobjects/OneBlockIslandsTest.java
@@ -1,6 +1,9 @@
 package world.bentobox.aoneblock.dataobjects;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import java.util.List;
 import java.util.Queue;
diff --git a/src/test/java/world/bentobox/aoneblock/listeners/CheckPhaseTest.java b/src/test/java/world/bentobox/aoneblock/listeners/CheckPhaseTest.java
index 6ef0b605..c5a91791 100644
--- a/src/test/java/world/bentobox/aoneblock/listeners/CheckPhaseTest.java
+++ b/src/test/java/world/bentobox/aoneblock/listeners/CheckPhaseTest.java
@@ -1,8 +1,6 @@
 package world.bentobox.aoneblock.listeners;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -55,7 +53,7 @@
  *
  */
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({Bukkit.class, BentoBox.class, DatabaseSetup.class, Util.class})
+@PrepareForTest({ Bukkit.class, BentoBox.class, DatabaseSetup.class, Util.class })
 public class CheckPhaseTest {
 
     @Mock
@@ -88,60 +86,56 @@ public class CheckPhaseTest {
     @Mock
     private Level level;
 
-
-
     /**
      * @throws java.lang.Exception
      */
     @Before
     public void setUp() throws Exception {
-        // Bukkit
-        PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
-        // Set up plugin
-        Whitebox.setInternalState(BentoBox.class, "instance", plugin);
-
-        // Addon
-        when(addon.getPlugin()).thenReturn(plugin);
-        when(addon.getOneBlockManager()).thenReturn(obm);
-        when(addon.getPlayers()).thenReturn(pm);
-        when(addon.getOverWorld()).thenReturn(world);
-        when(addon.getIslands()).thenReturn(im);
-        when(addon.inWorld(world)).thenReturn(true);
-        when(addon.getBlockListener()).thenReturn(blis);
-
-        // Player
-        when(player.getUniqueId()).thenReturn(UUID.randomUUID());
-        when(player.getName()).thenReturn("tastybento");
-        when(player.isOnline()).thenReturn(true);
-        when(player.getWorld()).thenReturn(world);
-        User.setPlugin(plugin);
-        user = User.getInstance(player);
-
-        // Island
-        island = new Island();
-        island.setOwner(UUID.randomUUID());
-        island.setName("island_name");
-        // Players Manager
-        when(pm.getName(any())).thenReturn("tastybento2");
-        when(pm.getUser(any(UUID.class))).thenReturn(user);
-
-        // Bank
-        BankManager bm = mock(BankManager.class);
-        when(bank.getBankManager()).thenReturn(bm);
-        // Phat balance to start
-        when(bm.getBalance(island)).thenReturn(new Money(100000D));
-        when(addon.getAddonByName("Bank")).thenReturn(Optional.of(bank));
-        // Level
-        when(level.getIslandLevel(eq(world), any())).thenReturn(1000L);
-        when(addon.getAddonByName("Level")).thenReturn(Optional.of(level));
-
-
-        // Placeholders
-        when(plugin.getPlaceholdersManager()).thenReturn(phm);
-        when(phm.replacePlaceholders(any(), anyString())).thenAnswer(a -> (String)a.getArgument(1, String.class));
-
-
-        bl = new CheckPhase(addon, blis);
+	// Bukkit
+	PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
+	// Set up plugin
+	Whitebox.setInternalState(BentoBox.class, "instance", plugin);
+
+	// Addon
+	when(addon.getPlugin()).thenReturn(plugin);
+	when(addon.getOneBlockManager()).thenReturn(obm);
+	when(addon.getPlayers()).thenReturn(pm);
+	when(addon.getOverWorld()).thenReturn(world);
+	when(addon.getIslands()).thenReturn(im);
+	when(addon.inWorld(world)).thenReturn(true);
+	when(addon.getBlockListener()).thenReturn(blis);
+
+	// Player
+	when(player.getUniqueId()).thenReturn(UUID.randomUUID());
+	when(player.getName()).thenReturn("tastybento");
+	when(player.isOnline()).thenReturn(true);
+	when(player.getWorld()).thenReturn(world);
+	User.setPlugin(plugin);
+	user = User.getInstance(player);
+
+	// Island
+	island = new Island();
+	island.setOwner(UUID.randomUUID());
+	island.setName("island_name");
+	// Players Manager
+	when(pm.getName(any())).thenReturn("tastybento2");
+	when(pm.getUser(any(UUID.class))).thenReturn(user);
+
+	// Bank
+	BankManager bm = mock(BankManager.class);
+	when(bank.getBankManager()).thenReturn(bm);
+	// Phat balance to start
+	when(bm.getBalance(island)).thenReturn(new Money(100000D));
+	when(addon.getAddonByName("Bank")).thenReturn(Optional.of(bank));
+	// Level
+	when(level.getIslandLevel(eq(world), any())).thenReturn(1000L);
+	when(addon.getAddonByName("Level")).thenReturn(Optional.of(level));
+
+	// Placeholders
+	when(plugin.getPlaceholdersManager()).thenReturn(phm);
+	when(phm.replacePlaceholders(any(), anyString())).thenAnswer(a -> (String) a.getArgument(1, String.class));
+
+	bl = new CheckPhase(addon, blis);
     }
 
     /**
@@ -151,99 +145,107 @@ public void setUp() throws Exception {
     public void tearDown() throws Exception {
     }
 
-
     /**
-     * Test method for {@link world.bentobox.aoneblock.listeners.CheckPhase#checkPhase(Player, Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}
+     * Test method for
+     * {@link world.bentobox.aoneblock.listeners.CheckPhase#setNewPhase(Player, Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}
      */
     @Test
-    public void testCheckPhase() {
-        // Set up that a phase has been completed
-        is = new OneBlockIslands(UUID.randomUUID().toString());
-        is.setPhaseName("Previous");
-        is.setBlockNumber(500);
-        is.setLifetime(500L);
-        // The phase the user has just moved to
-        phase = new OneBlockPhase("500");
-        phase.setPhaseName("Next Phase");
-        phase.setStartCommands(List.of("start1", "start2"));
-
-        // The previous phase
-        OneBlockPhase previous = mock(OneBlockPhase.class);
-        when(previous.getPhaseName()).thenReturn("Previous");
-
-        when(obm.getPhase("Previous")).thenReturn(Optional.of(previous));
-
-        assertTrue(bl.checkPhase(player, island, is, phase));
-        // Verify commands run
-        verify(previous).getEndCommands();
-        verify(previous).getFirstTimeEndCommands();
-        // Verify title shown
-        verify(player).sendTitle("Next Phase", null, -1, -1, -1);
+    public void testSetNewPhase() {
+	// Set up that a phase has been completed
+	is = new OneBlockIslands(UUID.randomUUID().toString());
+	is.setPhaseName("Previous");
+	is.setBlockNumber(500);
+	is.setLifetime(500L);
+	// The phase the user has just moved to
+	phase = new OneBlockPhase("500");
+	phase.setPhaseName("Next Phase");
+	phase.setStartCommands(List.of("start1", "start2"));
+
+	// The previous phase
+	OneBlockPhase previous = mock(OneBlockPhase.class);
+	when(previous.getPhaseName()).thenReturn("Previous");
+
+	when(obm.getPhase("Previous")).thenReturn(Optional.of(previous));
+
+	bl.setNewPhase(player, island, is, phase);
+	// Verify commands run
+	verify(previous).getEndCommands();
+	verify(previous).getFirstTimeEndCommands();
+	// Verify phase name change
+	assertEquals("Next Phase", is.getPhaseName());
+	// Verify title shown
+	verify(player).sendTitle("Next Phase", null, -1, -1, -1);
 
     }
 
     /**
-     * Test method for {@link world.bentobox.aoneblock.listeners.CheckPhase#checkPhase(Player, Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}
+     * Test method for
+     * {@link world.bentobox.aoneblock.listeners.CheckPhase#setNewPhase(Player, Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}
      */
     @Test
-    public void testCheckPhaseSecondTime() {
-        // Set up that a phase has been completed
-        is = new OneBlockIslands(UUID.randomUUID().toString());
-        is.setPhaseName("Previous");
-        is.setBlockNumber(500);
-        is.setLifetime(10500L);
-        // The phase the user has just moved to
-        phase = new OneBlockPhase("500");
-        phase.setPhaseName("Next Phase");
-        phase.setStartCommands(List.of("start1", "start2"));
-
-        // The previous phase
-        OneBlockPhase previous = mock(OneBlockPhase.class);
-        when(previous.getPhaseName()).thenReturn("Previous");
-
-        when(obm.getPhase("Previous")).thenReturn(Optional.of(previous));
-
-        assertTrue(bl.checkPhase(player, island, is, phase));
-        // Verify commands run
-        verify(previous).getEndCommands();
-        verify(previous, never()).getFirstTimeEndCommands();
-        // Verify title shown
-        verify(player).sendTitle("Next Phase", null, -1, -1, -1);
+    public void testSetNewPhaseSecondTime() {
+	// Set up that a phase has been completed
+	is = new OneBlockIslands(UUID.randomUUID().toString());
+	is.setPhaseName("Previous");
+	is.setBlockNumber(500);
+	is.setLifetime(10500L);
+	// The phase the user has just moved to
+	phase = new OneBlockPhase("500");
+	phase.setPhaseName("Next Phase");
+	phase.setStartCommands(List.of("start1", "start2"));
+
+	// The previous phase
+	OneBlockPhase previous = mock(OneBlockPhase.class);
+	when(previous.getPhaseName()).thenReturn("Previous");
+
+	when(obm.getPhase("Previous")).thenReturn(Optional.of(previous));
+
+	bl.setNewPhase(player, island, is, phase);
+	// Verify commands run
+	verify(previous).getEndCommands();
+	verify(previous, never()).getFirstTimeEndCommands();
+	// Verify phase name change
+	assertEquals("Next Phase", is.getPhaseName());
+	// Verify title shown
+	verify(player).sendTitle("Next Phase", null, -1, -1, -1);
 
     }
 
     /**
-     * Test method for {@link world.bentobox.aoneblock.listeners.CheckPhase#checkPhase(Player, Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}
+     * Test method for
+     * {@link world.bentobox.aoneblock.listeners.CheckPhase#setNewPhase(Player, Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}
      */
     @Test
-    public void testCheckPhaseNullPlayer() {
-        // Set up that a phase has been completed
-        is = new OneBlockIslands(UUID.randomUUID().toString());
-        is.setPhaseName("Previous");
-        is.setBlockNumber(500);
-        is.setLifetime(500L);
-        // The phase the user has just moved to
-        phase = new OneBlockPhase("500");
-        phase.setPhaseName("Next Phase");
-        phase.setStartCommands(List.of("start1", "start2"));
-
-        // The previous phase
-        OneBlockPhase previous = mock(OneBlockPhase.class);
-        when(previous.getPhaseName()).thenReturn("Previous");
-
-        when(obm.getPhase("Previous")).thenReturn(Optional.of(previous));
-
-        assertTrue(bl.checkPhase(null, island, is, phase));
-        // Verify commands run
-        verify(previous).getEndCommands();
-        verify(previous).getFirstTimeEndCommands();
-        // Verify title shown
-        verify(player).sendTitle("Next Phase", null, -1, -1, -1);
+    public void testSetNewPhaseNullPlayer() {
+	// Set up that a phase has been completed
+	is = new OneBlockIslands(UUID.randomUUID().toString());
+	is.setPhaseName("Previous");
+	is.setBlockNumber(500);
+	is.setLifetime(500L);
+	// The phase the user has just moved to
+	phase = new OneBlockPhase("500");
+	phase.setPhaseName("Next Phase");
+	phase.setStartCommands(List.of("start1", "start2"));
+
+	// The previous phase
+	OneBlockPhase previous = mock(OneBlockPhase.class);
+	when(previous.getPhaseName()).thenReturn("Previous");
+
+	when(obm.getPhase("Previous")).thenReturn(Optional.of(previous));
+
+	bl.setNewPhase(null, island, is, phase);
+	// Verify commands run
+	verify(previous).getEndCommands();
+	verify(previous).getFirstTimeEndCommands();
+	// Verify phase name change
+	assertEquals("Next Phase", is.getPhaseName());
+	// Verify title shown
+	verify(player).sendTitle("Next Phase", null, -1, -1, -1);
 
     }
 
     /**
-     * Test method for {@link world.bentobox.aoneblock.listeners.CheckPhase#checkPhase(Player, Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}
+     * Test method for {@link world.bentobox.aoneblock.listeners.CheckPhase#setNewPhase(Player, Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}
      */
     @Test
     public void testCheckPhaseNPCPlayer() {
@@ -264,57 +266,41 @@ public void testCheckPhaseNPCPlayer() {
 
         when(obm.getPhase("Previous")).thenReturn(Optional.of(previous));
 
-        assertTrue(bl.checkPhase(player, island, is, phase));
+        bl.setNewPhase(player, island, is, phase);
         // Verify commands run
         verify(previous).getEndCommands();
         verify(previous).getFirstTimeEndCommands();
+        // Verify phase name change
+        assertEquals("Next Phase", is.getPhaseName());
         // Verify title shown
         verify(player).sendTitle("Next Phase", null, -1, -1, -1);
 
     }
 
     /**
-     * Test method for {@link world.bentobox.aoneblock.listeners.CheckPhase#checkPhase(Player, Island, world.bentobox.aoneblock.dataobjects.OneBlockIslands, world.bentobox.aoneblock.oneblocks.OneBlockPhase)}
-     */
-    @Test
-    public void testCheckSamePhase() {
-        is = new OneBlockIslands(UUID.randomUUID().toString());
-        is.setPhaseName("Previous");
-        is.setBlockNumber(500);
-        is.setLifetime(500L);
-        // The phase the user has just moved to
-        phase = new OneBlockPhase("500");
-        phase.setPhaseName("Previous");
-
-        assertFalse(bl.checkPhase(player, island, is, phase));
-
-    }
-
-    /**
-     * Test method for {@link world.bentobox.aoneblock.listeners.BlockListener#replacePlaceholders(org.bukkit.entity.Player, java.lang.String, java.lang.String, world.bentobox.bentobox.database.objects.Island, java.util.List)}.
+     * Test method for
+     * {@link world.bentobox.aoneblock.listeners.BlockListener#replacePlaceholders(org.bukkit.entity.Player, java.lang.String, java.lang.String, world.bentobox.bentobox.database.objects.Island, java.util.List)}.
      */
     @Test
     public void testReplacePlaceholders() {
-        // Commands
-        /*
-         * [island] - Island name
-         * [owner] - Island owner's name
-         * [player] - The name of the player who broke the block triggering the commands
-         * [phase] - the name of this phase
-         * [blocks] - the number of blocks broken
-         * [level] - island level (Requires Levels Addon)
-         * [bank-balance] - island bank balance (Requires Bank Addon)
-         * [eco-balance] - player's economy balance (Requires Vault and an economy plugin)
-
-         */
-        List commandList = new ArrayList<>();
-
-        commandList.add("no replacement");
-        commandList.add("[island] [owner] [phase] [blocks] [level] [bank-balance] [eco-balance]");
-        List r = bl.replacePlaceholders(player, "phaseName", "1000", island, commandList);
-        assertEquals(2, r.size());
-        assertEquals("no replacement", r.get(0));
-        assertEquals("island_name tastybento2 phaseName 1000 1000 100000.0 0.0", r.get(1));
-        verify(phm, times(2)).replacePlaceholders(eq(player), any());
+	// Commands
+	/*
+	 * [island] - Island name [owner] - Island owner's name [player] - The name of
+	 * the player who broke the block triggering the commands [phase] - the name of
+	 * this phase [blocks] - the number of blocks broken [level] - island level
+	 * (Requires Levels Addon) [bank-balance] - island bank balance (Requires Bank
+	 * Addon) [eco-balance] - player's economy balance (Requires Vault and an
+	 * economy plugin)
+	 * 
+	 */
+	List commandList = new ArrayList<>();
+
+	commandList.add("no replacement");
+	commandList.add("[island] [owner] [phase] [blocks] [level] [bank-balance] [eco-balance]");
+	List r = bl.replacePlaceholders(player, "phaseName", "1000", island, commandList);
+	assertEquals(2, r.size());
+	assertEquals("no replacement", r.get(0));
+	assertEquals("island_name tastybento2 phaseName 1000 1000 100000.0 0.0", r.get(1));
+	verify(phm, times(2)).replacePlaceholders(eq(player), any());
     }
 }
diff --git a/src/test/java/world/bentobox/aoneblock/listeners/NoBlockHandlerTest.java b/src/test/java/world/bentobox/aoneblock/listeners/NoBlockHandlerTest.java
index 10c4363c..5ad0baa6 100644
--- a/src/test/java/world/bentobox/aoneblock/listeners/NoBlockHandlerTest.java
+++ b/src/test/java/world/bentobox/aoneblock/listeners/NoBlockHandlerTest.java
@@ -14,6 +14,7 @@
 import org.bukkit.block.Block;
 import org.bukkit.entity.Player;
 import org.bukkit.event.player.PlayerRespawnEvent;
+import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -31,16 +32,16 @@
  */
 @RunWith(PowerMockRunner.class)
 public class NoBlockHandlerTest {
-    
+
     private static final UUID ID = UUID.randomUUID();
-    
+
     @Mock
     private AOneBlock aob;
     @Mock
     private Player p;
-    
+
     private NoBlockHandler nbh;
-    
+
     @Mock
     private Block block;
     @Mock
@@ -54,7 +55,6 @@ public class NoBlockHandlerTest {
     @Mock
     private World world;
 
-
     /**
      * @throws java.lang.Exception
      */
@@ -92,36 +92,38 @@ public void tearDown() throws Exception {
     }
 
     /**
-     * Test method for {@link world.bentobox.aoneblock.listeners.NoBlockHandler#NoBlockHandler(world.bentobox.aoneblock.AOneBlock)}.
+     * Test method for
+     * {@link world.bentobox.aoneblock.listeners.NoBlockHandler#NoBlockHandler(world.bentobox.aoneblock.AOneBlock)}.
      */
     @Test
     public void testNoBlockHandler() {
-        assertNotNull(nbh);
+	assertNotNull(nbh);
     }
 
     /**
-     * Test method for {@link world.bentobox.aoneblock.listeners.NoBlockHandler#onRespawn(org.bukkit.event.player.PlayerRespawnEvent)}.
+     * Test method for
+     * {@link world.bentobox.aoneblock.listeners.NoBlockHandler#onRespawn(org.bukkit.event.player.PlayerRespawnEvent)}.
      */
     @Test
     public void testOnRespawnSolidBlock() {
-        PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false);
-        nbh.onRespawn(event);
-        verify(block, never()).setType(any(Material.class));
-        
+	PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, RespawnReason.DEATH);
+	nbh.onRespawn(event);
+	verify(block, never()).setType(any(Material.class));
+
     }
-    
+
     /**
      * Test method for {@link world.bentobox.aoneblock.listeners.NoBlockHandler#onRespawn(org.bukkit.event.player.PlayerRespawnEvent)}.
      */
     @Test
     public void testOnRespawnAirBlock() {
         when(block.isEmpty()).thenReturn(true);
-        PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false);
+        PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, RespawnReason.DEATH);
         nbh.onRespawn(event);
         verify(block).setType(any(Material.class));
         
     }
-    
+
     /**
      * Test method for {@link world.bentobox.aoneblock.listeners.NoBlockHandler#onRespawn(org.bukkit.event.player.PlayerRespawnEvent)}.
      */
@@ -129,12 +131,12 @@ public void testOnRespawnAirBlock() {
     public void testOnRespawnAirBlockWrongWorld() {
         when(aob.inWorld(world)).thenReturn(false);
         when(block.isEmpty()).thenReturn(true);
-        PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false);
+        PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, RespawnReason.DEATH);
         nbh.onRespawn(event);
         verify(block, never()).setType(any(Material.class));
         
     }
-    
+
     /**
      * Test method for {@link world.bentobox.aoneblock.listeners.NoBlockHandler#onRespawn(org.bukkit.event.player.PlayerRespawnEvent)}.
      */
@@ -142,11 +144,10 @@ public void testOnRespawnAirBlockWrongWorld() {
     public void testOnRespawnAirBlockNoIsland() {
         when(im.getIsland(world, ID)).thenReturn(null);
         when(block.isEmpty()).thenReturn(true);
-        PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false);
+        PlayerRespawnEvent event = new PlayerRespawnEvent(p, location, false, false, RespawnReason.DEATH);
         nbh.onRespawn(event);
         verify(block, never()).setType(any(Material.class));
         
     }
 
-
 }

From aa222a5ddb99fd1d2c24e8d58cc836f51f30da67 Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Sat, 25 Nov 2023 08:53:07 -0800
Subject: [PATCH 30/42] Make a potential null ""

The only time this might be null is after loading from the database, and
is rare.
---
 .../aoneblock/listeners/CheckPhase.java       | 231 +++++++++---------
 1 file changed, 119 insertions(+), 112 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java
index 337c75e3..c497dcec 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java
@@ -1,6 +1,7 @@
 package world.bentobox.aoneblock.listeners;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 import org.bukkit.World;
@@ -22,6 +23,7 @@
 
 /**
  * Performs end of phase checking
+ * 
  * @author tastybento
  *
  */
@@ -31,15 +33,14 @@ public class CheckPhase {
     private final OneBlocksManager oneBlocksManager;
     private final BlockListener blockListener;
 
-
     /**
-     * @param addon AOneBlock
+     * @param addon         AOneBlock
      * @param blockListener
      */
     public CheckPhase(AOneBlock addon, BlockListener blockListener) {
-        this.addon = addon;
-        this.oneBlocksManager = addon.getOneBlockManager();
-        this.blockListener = blockListener;
+	this.addon = addon;
+	this.oneBlocksManager = addon.getOneBlockManager();
+	this.blockListener = blockListener;
 
     }
 
@@ -51,47 +52,44 @@ public CheckPhase(AOneBlock addon, BlockListener blockListener) {
      * @param is     - OneBlockIslands object
      * @param phase  - current phase
      */
-    void setNewPhase(
-            @Nullable Player player,
-            @NonNull Island i,
-            @NonNull OneBlockIslands is,
-            @NonNull OneBlockPhase phase
-    ) {
-        // Handle NPCs
-        User user;
-        if (player == null || player.hasMetadata("NPC")) {
-            // Default to the owner
-            user = addon.getPlayers().getUser(i.getOwner());
-        } else {
-            user = User.getInstance(player);
-        }
-
-        String newPhaseName = phase.getPhaseName();
-
-        // Run previous phase end commands
-        oneBlocksManager.getPhase(is.getPhaseName()).ifPresent(oldPhase -> {
-            String oldPhaseName = oldPhase.getPhaseName() == null ? "" : oldPhase.getPhaseName();
-            Util.runCommands(user,
-                    replacePlaceholders(player, oldPhaseName, phase.getBlockNumber(), i, oldPhase.getEndCommands()),
-                    "Commands run for end of " + oldPhaseName);
-            // If first time
-            if (is.getBlockNumber() >= is.getLifetime()) {
-                Util.runCommands(user,
-                        replacePlaceholders(player, oldPhaseName, phase.getBlockNumber(), i, oldPhase.getFirstTimeEndCommands()),
-                        "Commands run for first time completing " + oldPhaseName);
-            }
-        });
-        // Set the phase name
-        is.setPhaseName(newPhaseName);
-        if (user.isPlayer() && user.isOnline() && addon.inWorld(user.getWorld())) {
-            user.getPlayer().sendTitle(newPhaseName, null, -1, -1, -1);
-        }
-        // Run phase start commands
-        Util.runCommands(user,
-                replacePlaceholders(player, newPhaseName, phase.getBlockNumber(), i, phase.getStartCommands()),
-                "Commands run for start of " + newPhaseName);
-
-        blockListener.saveIsland(i);
+    void setNewPhase(@Nullable Player player, @NonNull Island i, @NonNull OneBlockIslands is,
+	    @NonNull OneBlockPhase phase) {
+	// Handle NPCs
+	User user;
+	if (player == null || player.hasMetadata("NPC")) {
+	    // Default to the owner
+	    user = addon.getPlayers().getUser(i.getOwner());
+	} else {
+	    user = User.getInstance(player);
+	}
+
+	String newPhaseName = Objects.requireNonNullElse(phase.getPhaseName(), "");
+
+	// Run previous phase end commands
+	oneBlocksManager.getPhase(is.getPhaseName()).ifPresent(oldPhase -> {
+	    String oldPhaseName = oldPhase.getPhaseName() == null ? "" : oldPhase.getPhaseName();
+	    Util.runCommands(user,
+		    replacePlaceholders(player, oldPhaseName, phase.getBlockNumber(), i, oldPhase.getEndCommands()),
+		    "Commands run for end of " + oldPhaseName);
+	    // If first time
+	    if (is.getBlockNumber() >= is.getLifetime()) {
+		Util.runCommands(user,
+			replacePlaceholders(player, oldPhaseName, phase.getBlockNumber(), i,
+				oldPhase.getFirstTimeEndCommands()),
+			"Commands run for first time completing " + oldPhaseName);
+	    }
+	});
+	// Set the phase name
+	is.setPhaseName(newPhaseName);
+	if (user.isPlayer() && user.isOnline() && addon.inWorld(user.getWorld())) {
+	    user.getPlayer().sendTitle(newPhaseName, null, -1, -1, -1);
+	}
+	// Run phase start commands
+	Util.runCommands(user,
+		replacePlaceholders(player, newPhaseName, phase.getBlockNumber(), i, phase.getStartCommands()),
+		"Commands run for start of " + newPhaseName);
+
+	blockListener.saveIsland(i);
     }
 
     /**
@@ -103,58 +101,67 @@ void setNewPhase(
      * @param world  - world
      * @return true if the player cannot proceed to the next phase.
      */
-    protected boolean phaseRequirementsFail(@Nullable Player player, @NonNull Island i, @NonNull OneBlockIslands is, OneBlockPhase phase, @NonNull World world) {
-        if (phase.getRequirements().isEmpty()) {
-            return false;
-        }
-        // Check requirements
-        boolean blocked = false;
-        for (Requirement r : phase.getRequirements()) {
-            boolean b = switch (r.getType()) {
-            case LEVEL -> addon.getAddonByName("Level").map(l -> {
-                if (((Level) l).getIslandLevel(world, i.getOwner()) < r.getLevel()) {
-                    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-level", TextVariables.NUMBER, String.valueOf(r.getLevel()));
-                    return true;
-                }
-                return false;
-            }).orElse(false);
-            case BANK -> addon.getAddonByName("Bank").map(l -> {
-                if (((Bank) l).getBankManager().getBalance(i).getValue() < r.getBank()) {
-                    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-bank-balance", TextVariables.NUMBER, String.valueOf(r.getBank()));
-                    return true;
-                }
-                return false;
-            }).orElse(false);
-            case ECO -> addon.getPlugin().getVault().map(l -> {
-                if (l.getBalance(User.getInstance(player), world) < r.getEco()) {
-                    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-funds", TextVariables.NUMBER, String.valueOf(r.getEco()));
-                    return true;
-                }
-                return false;
-            }).orElse(false);
-            case PERMISSION -> {
-                if (player != null && !player.hasPermission(r.getPermission())) {
-                    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-permission", TextVariables.NAME, String.valueOf(r.getPermission()));
-                    yield true;
-                }
-                yield false;
-            }
-            case COOLDOWN -> {
-                long remainingTime = r.getCooldown() - (System.currentTimeMillis() - is.getLastPhaseChangeTime()) / 1000;
-                if(remainingTime > 0){
-                    User.getInstance(player).sendMessage("aoneblock.phase.cooldown", TextVariables.NUMBER, String.valueOf(remainingTime));
-                    yield true;
-                }
-                yield false;
-            }
-            };
-            if (b) blocked = true;
-        }
-        return blocked;
+    protected boolean phaseRequirementsFail(@Nullable Player player, @NonNull Island i, @NonNull OneBlockIslands is,
+	    OneBlockPhase phase, @NonNull World world) {
+	if (phase.getRequirements().isEmpty()) {
+	    return false;
+	}
+	// Check requirements
+	boolean blocked = false;
+	for (Requirement r : phase.getRequirements()) {
+	    boolean b = switch (r.getType()) {
+	    case LEVEL -> addon.getAddonByName("Level").map(l -> {
+		if (((Level) l).getIslandLevel(world, i.getOwner()) < r.getLevel()) {
+		    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-level", TextVariables.NUMBER,
+			    String.valueOf(r.getLevel()));
+		    return true;
+		}
+		return false;
+	    }).orElse(false);
+	    case BANK -> addon.getAddonByName("Bank").map(l -> {
+		if (((Bank) l).getBankManager().getBalance(i).getValue() < r.getBank()) {
+		    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-bank-balance",
+			    TextVariables.NUMBER, String.valueOf(r.getBank()));
+		    return true;
+		}
+		return false;
+	    }).orElse(false);
+	    case ECO -> addon.getPlugin().getVault().map(l -> {
+		if (l.getBalance(User.getInstance(player), world) < r.getEco()) {
+		    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-funds", TextVariables.NUMBER,
+			    String.valueOf(r.getEco()));
+		    return true;
+		}
+		return false;
+	    }).orElse(false);
+	    case PERMISSION -> {
+		if (player != null && !player.hasPermission(r.getPermission())) {
+		    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-permission", TextVariables.NAME,
+			    String.valueOf(r.getPermission()));
+		    yield true;
+		}
+		yield false;
+	    }
+	    case COOLDOWN -> {
+		long remainingTime = r.getCooldown()
+			- (System.currentTimeMillis() - is.getLastPhaseChangeTime()) / 1000;
+		if (remainingTime > 0) {
+		    User.getInstance(player).sendMessage("aoneblock.phase.cooldown", TextVariables.NUMBER,
+			    String.valueOf(remainingTime));
+		    yield true;
+		}
+		yield false;
+	    }
+	    };
+	    if (b)
+		blocked = true;
+	}
+	return blocked;
     }
 
     /**
      * Replaces placeholders in commands.
+     * 
      * 
      * [island] - Island name
      * [owner] - Island owner's name
@@ -174,23 +181,23 @@ protected boolean phaseRequirementsFail(@Nullable Player player, @NonNull Island
      * @return list of commands with placeholders replaced
      */
     @NonNull
-    List replacePlaceholders(@Nullable Player player, @NonNull String phaseName, @NonNull String phaseNumber, @NonNull Island i, List commands) {
-        return commands.stream()
-                .map(c -> {
-                    long level = addon.getAddonByName("Level").map(l -> ((Level) l).getIslandLevel(addon.getOverWorld(), i.getOwner())).orElse(0L);
-                    double balance = addon.getAddonByName("Bank").map(b -> ((Bank) b).getBankManager().getBalance(i).getValue()).orElse(0D);
-                    double ecoBalance = addon.getPlugin().getVault().map(v -> v.getBalance(User.getInstance(player), addon.getOverWorld())).orElse(0D);
-
-                    return c.replace("[island]", i.getName() == null ? "" : i.getName())
-                            .replace("[owner]", addon.getPlayers().getName(i.getOwner()))
-                            .replace("[phase]", phaseName)
-                            .replace("[blocks]", phaseNumber)
-                            .replace("[level]", String.valueOf(level))
-                            .replace("[bank-balance]", String.valueOf(balance))
-                            .replace("[eco-balance]", String.valueOf(ecoBalance));
-
-                })
-                .map(c -> addon.getPlugin().getPlaceholdersManager().replacePlaceholders(player, c))
-                .collect(Collectors.toList());
+    List replacePlaceholders(@Nullable Player player, @NonNull String phaseName, @NonNull String phaseNumber,
+	    @NonNull Island i, List commands) {
+	return commands.stream().map(c -> {
+	    long level = addon.getAddonByName("Level")
+		    .map(l -> ((Level) l).getIslandLevel(addon.getOverWorld(), i.getOwner())).orElse(0L);
+	    double balance = addon.getAddonByName("Bank").map(b -> ((Bank) b).getBankManager().getBalance(i).getValue())
+		    .orElse(0D);
+	    double ecoBalance = addon.getPlugin().getVault()
+		    .map(v -> v.getBalance(User.getInstance(player), addon.getOverWorld())).orElse(0D);
+
+	    return c.replace("[island]", i.getName() == null ? "" : i.getName())
+		    .replace("[owner]", addon.getPlayers().getName(i.getOwner())).replace("[phase]", phaseName)
+		    .replace("[blocks]", phaseNumber).replace("[level]", String.valueOf(level))
+		    .replace("[bank-balance]", String.valueOf(balance))
+		    .replace("[eco-balance]", String.valueOf(ecoBalance));
+
+	}).map(c -> addon.getPlugin().getPlaceholdersManager().replacePlaceholders(player, c))
+		.collect(Collectors.toList());
     }
 }

From f936bbbf0f8e33eea0ef83f6d9014db9366768ea Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Sat, 25 Nov 2023 08:54:40 -0800
Subject: [PATCH 31/42] Make "aoneblock.placeholders.infinite" a constant

---
 .../world/bentobox/aoneblock/PlaceholdersManager.java     | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java b/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java
index 1517b29d..05750d79 100644
--- a/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java
+++ b/src/main/java/world/bentobox/aoneblock/PlaceholdersManager.java
@@ -10,6 +10,7 @@
 public class PlaceholdersManager {
 
     private static final TreeMap SCALE;
+    private static final String INFINITE = "aoneblock.placeholders.infinite";
     static {
 	SCALE = new TreeMap<>();
 	SCALE.put(0D, "&c╍╍╍╍╍╍╍╍");
@@ -118,8 +119,7 @@ public String getNextPhaseBlocksByLocation(User user) {
 	    return "";
 	return addon.getIslands().getProtectedIslandAt(Objects.requireNonNull(user.getLocation()))
 		.map(addon::getOneBlocksIsland).map(addon.getOneBlockManager()::getNextPhaseBlocks)
-		.map(num -> num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num))
-		.orElse("");
+		.map(num -> num < 0 ? user.getTranslation(INFINITE) : String.valueOf(num)).orElse("");
     }
 
     /**
@@ -136,7 +136,7 @@ public String getNextPhaseBlocks(User user) {
 	    return "";
 	}
 	int num = addon.getOneBlockManager().getNextPhaseBlocks(addon.getOneBlocksIsland(i));
-	return num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num);
+	return num < 0 ? user.getTranslation(INFINITE) : String.valueOf(num);
     }
 
     /**
@@ -153,7 +153,7 @@ public String getPhaseBlocks(User user) {
 	    return "";
 	}
 	int num = addon.getOneBlockManager().getPhaseBlocks(addon.getOneBlocksIsland(i));
-	return num < 0 ? user.getTranslation("aoneblock.placeholders.infinite") : String.valueOf(num);
+	return num < 0 ? user.getTranslation(INFINITE) : String.valueOf(num);
     }
 
     /**

From 6c41c7c8f3aa7a06d3a9c8c40c8057087c1aab25 Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Sat, 25 Nov 2023 08:56:56 -0800
Subject: [PATCH 32/42] Replaced instanceof check and cast

---
 .../dataobjects/OneBlockIslands.java          | 108 ++++++++++--------
 1 file changed, 60 insertions(+), 48 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java b/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java
index 4812defe..c4d0c0e4 100644
--- a/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java
+++ b/src/main/java/world/bentobox/aoneblock/dataobjects/OneBlockIslands.java
@@ -57,44 +57,44 @@ public class OneBlockIslands implements DataObject {
      */
     @NonNull
     public String getPhaseName() {
-        return phaseName == null ? "" : phaseName;
+	return phaseName == null ? "" : phaseName;
     }
 
     /**
      * @param phaseName the phaseName to set
      */
     public void setPhaseName(String phaseName) {
-        this.phaseName = phaseName;
+	this.phaseName = phaseName;
     }
 
     public OneBlockIslands(String uniqueId) {
-        this.uniqueId = uniqueId;
+	this.uniqueId = uniqueId;
     }
 
     /**
      * @return the blockNumber
      */
     public int getBlockNumber() {
-        return blockNumber;
+	return blockNumber;
     }
 
     /**
      * @param blockNumber the blockNumber to set
      */
     public void setBlockNumber(int blockNumber) {
-        this.blockNumber = blockNumber;
+	this.blockNumber = blockNumber;
     }
 
     /**
      * Increments the block number
      */
     public void incrementBlockNumber() {
-        // Ensure that lifetime is always at least blockNumber
-        if (this.lifetime < this.blockNumber) {
-            this.lifetime = this.blockNumber;
-        }
-        this.blockNumber++;
-        this.lifetime++;
+	// Ensure that lifetime is always at least blockNumber
+	if (this.lifetime < this.blockNumber) {
+	    this.lifetime = this.blockNumber;
+	}
+	this.blockNumber++;
+	this.lifetime++;
     }
 
     /**
@@ -102,117 +102,129 @@ public void incrementBlockNumber() {
      */
     @NonNull
     public String getHologram() {
-        return hologram == null ? "" : hologram;
+	return hologram == null ? "" : hologram;
     }
 
     /**
      * @param hologramLine Hologram line
      */
     public void setHologram(String hologramLine) {
-        this.hologram = hologramLine;
+	this.hologram = hologramLine;
     }
 
-    /* (non-Javadoc)
+    /*
+     * (non-Javadoc)
+     * 
      * @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId()
      */
     @Override
     public String getUniqueId() {
-        return uniqueId;
+	return uniqueId;
     }
 
-    /* (non-Javadoc)
-     * @see world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.String)
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.
+     * String)
      */
     @Override
     public void setUniqueId(String uniqueId) {
-        this.uniqueId = uniqueId;
+	this.uniqueId = uniqueId;
     }
 
     /**
      * @return the queue
      */
     public Queue getQueue() {
-        if (queue == null) queue = new LinkedList<>();
-        return queue;
+	if (queue == null)
+	    queue = new LinkedList<>();
+	return queue;
     }
 
     /**
      * Get a list of nearby upcoming mobs
+     * 
      * @param i - look ahead value
      * @return list of upcoming mobs
      */
     public List getNearestMob(int i) {
-        return getQueue().stream().limit(i).filter(obo ->
-                // Include OneBlockObjects that are Entity, or custom block of type MobCustomBlock
-                obo.isEntity() || (obo.isCustomBlock() && obo.getCustomBlock() instanceof MobCustomBlock)
-        ).map(obo -> {
-            if (obo.isCustomBlock() && obo.getCustomBlock() instanceof MobCustomBlock) {
-                return ((MobCustomBlock) obo.getCustomBlock()).getMob();
-            }
+	return getQueue().stream().limit(i).filter(obo ->
+	// Include OneBlockObjects that are Entity, or custom block of type
+	// MobCustomBlock
+	obo.isEntity() || (obo.isCustomBlock() && obo.getCustomBlock() instanceof MobCustomBlock)).map(obo -> {
+	    if (obo.isCustomBlock() && obo.getCustomBlock() instanceof MobCustomBlock mb) {
+		return mb.getMob();
+	    }
 
-            return obo.getEntityType();
-        }).toList();
+	    return obo.getEntityType();
+	}).toList();
     }
 
     /**
      * Adds a OneBlockObject to the queue
+     * 
      * @param nextBlock the OneBlockObject to be added
      */
     public void add(OneBlockObject nextBlock) {
-        getQueue().add(nextBlock);
+	getQueue().add(nextBlock);
     }
 
     /**
-     * Retrieves and removes the head of the queue, or returns null if this queue is empty.
-     * Inserts the specified element into the queue if it is possible to do so immediately without
-     * violating capacity restrictions, and throwing an 
+     * Retrieves and removes the head of the queue, or returns null if this queue is
+     * empty. Inserts the specified element into the queue if it is possible to do
+     * so immediately without violating capacity restrictions, and throwing an
      * {@code IllegalStateException} if no space is currently available.
+     * 
      * @param toAdd OneBlockObject
-     * @return OneBlockObject head of the queue, or returns null if this queue is empty.
+     * @return OneBlockObject head of the queue, or returns null if this queue is
+     *         empty.
      */
     public OneBlockObject pollAndAdd(OneBlockObject toAdd) {
-        getQueue();
-        OneBlockObject b = queue.poll();
-        queue.add(toAdd);
-        return b;
+	getQueue();
+	OneBlockObject b = queue.poll();
+	queue.add(toAdd);
+	return b;
     }
 
     /**
      * Clear the look ahead queue
      */
     public void clearQueue() {
-        getQueue().clear();
+	getQueue().clear();
     }
 
     /**
-     * @return the lifetime number of blocks broken not including the current block count
+     * @return the lifetime number of blocks broken not including the current block
+     *         count
      */
     public long getLifetime() {
-        // Ensure that lifetime is always at least blockNumber
-        if (this.lifetime < this.blockNumber) {
-            this.lifetime = this.blockNumber;
-        }
-        return lifetime;
+	// Ensure that lifetime is always at least blockNumber
+	if (this.lifetime < this.blockNumber) {
+	    this.lifetime = this.blockNumber;
+	}
+	return lifetime;
     }
 
     /**
      * @param lifetime lifetime number of blocks broken to set
      */
     public void setLifetime(long lifetime) {
-        this.lifetime = lifetime;
+	this.lifetime = lifetime;
     }
 
     /**
      * @return Timestamp of last phase change
      */
     public long getLastPhaseChangeTime() {
-        return this.lastPhaseChangeTime;
+	return this.lastPhaseChangeTime;
     }
 
     /**
      * @param lastPhaseChangeTime Timestamp of last phase change
      */
     public void setLastPhaseChangeTime(long lastPhaseChangeTime) {
-        this.lastPhaseChangeTime = lastPhaseChangeTime;
+	this.lastPhaseChangeTime = lastPhaseChangeTime;
     }
 }

From 271c2452039cdd72586c9283d27a67acdc415b2a Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Sat, 25 Nov 2023 09:01:18 -0800
Subject: [PATCH 33/42] Removed useless ++; it never get's used.

Put {} around the e.setCancelled(true) to make it clear that only this
statement is to be run.
---
 .../world/bentobox/aoneblock/listeners/BlockListener.java    | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
index d6234ecd..663c1205 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
@@ -343,12 +343,13 @@ private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player
 
 	// Get the next block
 	OneBlockObject nextBlock = (isCurrPhaseNew && phase.getFirstBlock() != null) ? phase.getFirstBlock()
-		: is.pollAndAdd(phase.getNextBlock(addon, blockNumber++));
+		: is.pollAndAdd(phase.getNextBlock(addon, blockNumber));
 
 	// Entity
 	if (nextBlock.isEntity()) {
-	    if (!(e instanceof EntitySpawnEvent))
+	    if (!(e instanceof EntitySpawnEvent)) {
 		e.setCancelled(true);
+	    }
 	    // Entity spawns do not increment the block number or break the block
 	    spawnEntity(nextBlock, block);
 	    // Fire event

From 05ebd017170a565c0dcd197596a3eb842e30a91a Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Sat, 25 Nov 2023 09:09:34 -0800
Subject: [PATCH 34/42] Refactored to reduce complexity.

---
 .../aoneblock/listeners/CheckPhase.java       | 115 ++++++++++--------
 1 file changed, 67 insertions(+), 48 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java
index c497dcec..0f3dc478 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java
@@ -103,60 +103,79 @@ void setNewPhase(@Nullable Player player, @NonNull Island i, @NonNull OneBlockIs
      */
     protected boolean phaseRequirementsFail(@Nullable Player player, @NonNull Island i, @NonNull OneBlockIslands is,
 	    OneBlockPhase phase, @NonNull World world) {
+
 	if (phase.getRequirements().isEmpty()) {
 	    return false;
 	}
-	// Check requirements
-	boolean blocked = false;
-	for (Requirement r : phase.getRequirements()) {
-	    boolean b = switch (r.getType()) {
-	    case LEVEL -> addon.getAddonByName("Level").map(l -> {
-		if (((Level) l).getIslandLevel(world, i.getOwner()) < r.getLevel()) {
-		    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-level", TextVariables.NUMBER,
-			    String.valueOf(r.getLevel()));
-		    return true;
-		}
-		return false;
-	    }).orElse(false);
-	    case BANK -> addon.getAddonByName("Bank").map(l -> {
-		if (((Bank) l).getBankManager().getBalance(i).getValue() < r.getBank()) {
-		    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-bank-balance",
-			    TextVariables.NUMBER, String.valueOf(r.getBank()));
-		    return true;
-		}
-		return false;
-	    }).orElse(false);
-	    case ECO -> addon.getPlugin().getVault().map(l -> {
-		if (l.getBalance(User.getInstance(player), world) < r.getEco()) {
-		    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-funds", TextVariables.NUMBER,
-			    String.valueOf(r.getEco()));
-		    return true;
-		}
-		return false;
-	    }).orElse(false);
-	    case PERMISSION -> {
-		if (player != null && !player.hasPermission(r.getPermission())) {
-		    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-permission", TextVariables.NAME,
-			    String.valueOf(r.getPermission()));
-		    yield true;
-		}
-		yield false;
+
+	return phase.getRequirements().stream().anyMatch(r -> checkRequirement(r, player, i, is, world));
+    }
+
+    private boolean checkRequirement(Requirement r, Player player, Island i, OneBlockIslands is, World world) {
+	return switch (r.getType()) {
+	case LEVEL -> checkLevelRequirement(r, player, i, world);
+	case BANK -> checkBankRequirement(r, player, i, world);
+	case ECO -> checkEcoRequirement(r, player, world);
+	case PERMISSION -> checkPermissionRequirement(r, player);
+	case COOLDOWN -> checkCooldownRequirement(r, player, is);
+	};
+    }
+
+    private boolean checkLevelRequirement(Requirement r, Player player, Island i, World world) {
+	// Level checking logic
+	return addon.getAddonByName("Level").map(l -> {
+	    if (((Level) l).getIslandLevel(world, i.getOwner()) < r.getLevel()) {
+		User.getInstance(player).sendMessage("aoneblock.phase.insufficient-level", TextVariables.NUMBER,
+			String.valueOf(r.getLevel()));
+		return true;
 	    }
-	    case COOLDOWN -> {
-		long remainingTime = r.getCooldown()
-			- (System.currentTimeMillis() - is.getLastPhaseChangeTime()) / 1000;
-		if (remainingTime > 0) {
-		    User.getInstance(player).sendMessage("aoneblock.phase.cooldown", TextVariables.NUMBER,
-			    String.valueOf(remainingTime));
-		    yield true;
-		}
-		yield false;
+	    return false;
+	}).orElse(false);
+    }
+
+    private boolean checkBankRequirement(Requirement r, Player player, Island i, World world) {
+	// Bank checking logic
+	return addon.getAddonByName("Bank").map(l -> {
+	    if (((Bank) l).getBankManager().getBalance(i).getValue() < r.getBank()) {
+		User.getInstance(player).sendMessage("aoneblock.phase.insufficient-bank-balance", TextVariables.NUMBER,
+			String.valueOf(r.getBank()));
+		return true;
 	    }
-	    };
-	    if (b)
-		blocked = true;
+	    return false;
+	}).orElse(false);
+    }
+
+    private boolean checkEcoRequirement(Requirement r, Player player, World world) {
+	// Eco checking logic
+	return addon.getPlugin().getVault().map(l -> {
+	    if (l.getBalance(User.getInstance(player), world) < r.getEco()) {
+		User.getInstance(player).sendMessage("aoneblock.phase.insufficient-funds", TextVariables.NUMBER,
+			String.valueOf(r.getEco()));
+		return true;
+	    }
+	    return false;
+	}).orElse(false);
+    }
+
+    private boolean checkPermissionRequirement(Requirement r, Player player) {
+	// Permission checking logic
+	if (player != null && !player.hasPermission(r.getPermission())) {
+	    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-permission", TextVariables.NAME,
+		    String.valueOf(r.getPermission()));
+	    return true;
+	}
+	return false;
+    }
+
+    private boolean checkCooldownRequirement(Requirement r, Player player, OneBlockIslands is) {
+	// Cooldown checking logic
+	long remainingTime = r.getCooldown() - (System.currentTimeMillis() - is.getLastPhaseChangeTime()) / 1000;
+	if (remainingTime > 0) {
+	    User.getInstance(player).sendMessage("aoneblock.phase.cooldown", TextVariables.NUMBER,
+		    String.valueOf(remainingTime));
+	    return true;
 	}
-	return blocked;
+	return false;
     }
 
     /**

From df46cd7c7735b1c9300218436d1b7f68306da540 Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Sat, 25 Nov 2023 09:28:49 -0800
Subject: [PATCH 35/42] Refactored to reduce complexity.

---
 .../aoneblock/oneblocks/OneBlocksManager.java | 1529 +++++++++--------
 1 file changed, 787 insertions(+), 742 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java
index af67be18..b028f3c2 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java
@@ -48,120 +48,124 @@
  */
 public class OneBlocksManager {
 
-	private static final String ONE_BLOCKS_YML = "oneblocks.yml";
-	private static final String NAME = "name";
-	private static final String BIOME = "biome";
-	private static final String FIRST_BLOCK = "firstBlock";
-	private static final String ICON = "icon";
-	private static final String FIXED_BLOCKS = "fixedBlocks";
-	private static final String HOLOGRAMS = "holograms";
-	private static final String CHESTS = "chests";
-	private static final String RARITY = "rarity";
-	private static final String CONTENTS = "contents";
-	private static final String MOBS = "mobs";
-	private static final String BLOCKS = "blocks";
-	private static final String PHASES = "phases";
-	private static final String GOTO_BLOCK = "gotoBlock";
-	private static final String START_COMMANDS = "start-commands";
-	private static final String END_COMMANDS = "end-commands";
-	private static final String END_COMMANDS_FIRST_TIME = "end-commands-first-time";
-	private static final String REQUIREMENTS = "requirements";
-	private final AOneBlock addon;
-	private TreeMap blockProbs;
-
-	/**
-	 * @param addon - addon
-	 */
-	public OneBlocksManager(AOneBlock addon) {
-		this.addon = addon;
-		// Initialize block probabilities
-		blockProbs = new TreeMap<>();
-	}
-
-	/**
-	 * Loads the game phases
-	 *
-	 * @throws IOException - if config file has bad syntax or migration fails
-	 */
-	public void loadPhases() throws IOException {
-		// Clear block probabilities
-		blockProbs = new TreeMap<>();
-		// Check for folder
-		File check = new File(addon.getDataFolder(), PHASES);
-		if (check.mkdirs()) {
-			addon.log(check.getAbsolutePath() + " does not exist, made folder.");
-			// Check for oneblock.yml
-			File oneblockFile = new File(addon.getDataFolder(), ONE_BLOCKS_YML);
-			if (oneblockFile.exists()) {
-				// Migrate to new folders
-				File renamedFile = new File(check, ONE_BLOCKS_YML);
-				Files.move(oneblockFile, renamedFile);
-				loadPhase(renamedFile);
-				this.saveOneBlockConfig();
-				java.nio.file.Files.delete(oneblockFile.toPath());
-				java.nio.file.Files.delete(renamedFile.toPath());
-				blockProbs.clear();
-			} else {
-				// Copy files from JAR
-				copyPhasesFromAddonJar(check);
-			}
-		}
-		// Get files in folder
-		// Filter for files ending with .yml
-		FilenameFilter ymlFilter = (dir, name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(".yml");
-		for (File phaseFile : Objects.requireNonNull(check.listFiles(ymlFilter))) {
-			loadPhase(phaseFile);
-		}
+    private static final String ONE_BLOCKS_YML = "oneblocks.yml";
+    private static final String NAME = "name";
+    private static final String BIOME = "biome";
+    private static final String FIRST_BLOCK = "firstBlock";
+    private static final String ICON = "icon";
+    private static final String FIXED_BLOCKS = "fixedBlocks";
+    private static final String HOLOGRAMS = "holograms";
+    private static final String CHESTS = "chests";
+    private static final String RARITY = "rarity";
+    private static final String CONTENTS = "contents";
+    private static final String MOBS = "mobs";
+    private static final String BLOCKS = "blocks";
+    private static final String PHASES = "phases";
+    private static final String GOTO_BLOCK = "gotoBlock";
+    private static final String START_COMMANDS = "start-commands";
+    private static final String END_COMMANDS = "end-commands";
+    private static final String END_COMMANDS_FIRST_TIME = "end-commands-first-time";
+    private static final String REQUIREMENTS = "requirements";
+    private static final String BLOCK = "Block ";
+    private static final String BUT_ALREADY_SET_TO = " but already set to ";
+    private static final String DUPLICATE = " Duplicate phase file?";
+    private final AOneBlock addon;
+    private TreeMap blockProbs;
+
+    /**
+     * @param addon - addon
+     */
+    public OneBlocksManager(AOneBlock addon) {
+	this.addon = addon;
+	// Initialize block probabilities
+	blockProbs = new TreeMap<>();
+    }
+
+    /**
+     * Loads the game phases
+     *
+     * @throws IOException - if config file has bad syntax or migration fails
+     */
+    public void loadPhases() throws IOException {
+	// Clear block probabilities
+	blockProbs = new TreeMap<>();
+	// Check for folder
+	File check = new File(addon.getDataFolder(), PHASES);
+	if (check.mkdirs()) {
+	    addon.log(check.getAbsolutePath() + " does not exist, made folder.");
+	    // Check for oneblock.yml
+	    File oneblockFile = new File(addon.getDataFolder(), ONE_BLOCKS_YML);
+	    if (oneblockFile.exists()) {
+		// Migrate to new folders
+		File renamedFile = new File(check, ONE_BLOCKS_YML);
+		Files.move(oneblockFile, renamedFile);
+		loadPhase(renamedFile);
+		this.saveOneBlockConfig();
+		java.nio.file.Files.delete(oneblockFile.toPath());
+		java.nio.file.Files.delete(renamedFile.toPath());
+		blockProbs.clear();
+	    } else {
+		// Copy files from JAR
+		copyPhasesFromAddonJar(check);
+	    }
+	}
+	// Get files in folder
+	// Filter for files ending with .yml
+	FilenameFilter ymlFilter = (dir, name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(".yml");
+	for (File phaseFile : Objects.requireNonNull(check.listFiles(ymlFilter))) {
+	    loadPhase(phaseFile);
 	}
+    }
 
-	/**
-	 * Copies phase files from the addon jar to the file system
-	 *
-	 * @param file - the file to copy
-	 */
-	void copyPhasesFromAddonJar(File file) {
-		try (JarFile jar = new JarFile(addon.getFile())) {
-			// Obtain any locale files, save them and update
-			Util.listJarFiles(jar, PHASES, ".yml").forEach(lf -> addon.saveResource(lf, file, false, true));
-		} catch (Exception e) {
-			addon.logError(e.getMessage());
-		}
+    /**
+     * Copies phase files from the addon jar to the file system
+     *
+     * @param file - the file to copy
+     */
+    void copyPhasesFromAddonJar(File file) {
+	try (JarFile jar = new JarFile(addon.getFile())) {
+	    // Obtain any locale files, save them and update
+	    Util.listJarFiles(jar, PHASES, ".yml").forEach(lf -> addon.saveResource(lf, file, false, true));
+	} catch (Exception e) {
+	    addon.logError(e.getMessage());
 	}
+    }
 
     private void loadPhase(File phaseFile) throws IOException {
-        addon.log("Loading " + phaseFile.getName());
-        // Load the config file
-        YamlConfiguration oneBlocks = new YamlConfiguration();
-        try {
-            oneBlocks.load(phaseFile);
-        } catch (Exception e) {
-            addon.logError(e.getMessage());
-            return;
-        }
-        for (String phaseStartBlockNumKey : oneBlocks.getKeys(false)) {
-            Integer phaseStartBlockNum = Integer.valueOf(phaseStartBlockNumKey);
-            OneBlockPhase obPhase = blockProbs.computeIfAbsent(phaseStartBlockNum, k -> new OneBlockPhase(phaseStartBlockNumKey));
-            // Get config Section
-            ConfigurationSection phaseConfig = oneBlocks.getConfigurationSection(phaseStartBlockNumKey);
-            // goto
-            if (phaseConfig.contains(GOTO_BLOCK)) {
-                obPhase.setGotoBlock(phaseConfig.getInt(GOTO_BLOCK, 0));
-                continue;
-            }
-            initBlock(phaseStartBlockNumKey, obPhase, phaseConfig);
-            // Blocks
-            addBlocks(obPhase, phaseConfig);
-            // Mobs
-            addMobs(obPhase, phaseConfig);
-            // Chests
-            addChests(obPhase, phaseConfig);
-            // Commands
-            addCommands(obPhase, phaseConfig);
-            // Requirements
-            addRequirements(obPhase, phaseConfig);
-            // Add to the map
-            blockProbs.put(phaseStartBlockNum, obPhase);
-        }
+	addon.log("Loading " + phaseFile.getName());
+	// Load the config file
+	YamlConfiguration oneBlocks = new YamlConfiguration();
+	try {
+	    oneBlocks.load(phaseFile);
+	} catch (Exception e) {
+	    addon.logError(e.getMessage());
+	    return;
+	}
+	for (String phaseStartBlockNumKey : oneBlocks.getKeys(false)) {
+	    Integer phaseStartBlockNum = Integer.valueOf(phaseStartBlockNumKey);
+	    OneBlockPhase obPhase = blockProbs.computeIfAbsent(phaseStartBlockNum,
+		    k -> new OneBlockPhase(phaseStartBlockNumKey));
+	    // Get config Section
+	    ConfigurationSection phaseConfig = oneBlocks.getConfigurationSection(phaseStartBlockNumKey);
+	    // goto
+	    if (phaseConfig.contains(GOTO_BLOCK)) {
+		obPhase.setGotoBlock(phaseConfig.getInt(GOTO_BLOCK, 0));
+		continue;
+	    }
+	    initBlock(phaseStartBlockNumKey, obPhase, phaseConfig);
+	    // Blocks
+	    addBlocks(obPhase, phaseConfig);
+	    // Mobs
+	    addMobs(obPhase, phaseConfig);
+	    // Chests
+	    addChests(obPhase, phaseConfig);
+	    // Commands
+	    addCommands(obPhase, phaseConfig);
+	    // Requirements
+	    addRequirements(obPhase, phaseConfig);
+	    // Add to the map
+	    blockProbs.put(phaseStartBlockNum, obPhase);
+	}
     }
 
     /**
@@ -173,693 +177,734 @@ private void loadPhase(File phaseFile) throws IOException {
      * @throws IOException if there's an error in the config file
      */
     void initBlock(String blockNumber, OneBlockPhase obPhase, ConfigurationSection phaseConfig) throws IOException {
-        // Set name
-        if (phaseConfig.contains(NAME, true)) {
-            if (obPhase.getPhaseName() != null) {
-                throw new IOException(
-                        "Block " + blockNumber + ": Phase name trying to be set to " + phaseConfig.getString(NAME)
-                                + " but already set to " + obPhase.getPhaseName() + ". Duplicate phase file?");
-            }
-            obPhase.setPhaseName(phaseConfig.getString(NAME, blockNumber));
-        }
-
-        // Set biome
-        if (phaseConfig.contains(BIOME, true)) {
-            if (obPhase.getPhaseBiome() != null) {
-                throw new IOException("Block " + blockNumber + ": Biome trying to be set to " + phaseConfig.getString(BIOME)
-                        + " but already set to " + obPhase.getPhaseBiome() + " Duplicate phase file?");
-            }
-            obPhase.setPhaseBiome(getBiome(phaseConfig.getString(BIOME)));
-        }
-
-        // Set first block
-        if (phaseConfig.contains(FIRST_BLOCK)) {
-            if (obPhase.getFirstBlock() != null) {
-                throw new IOException(
-                        "Block " + blockNumber + ": First block trying to be set to " + phaseConfig.getString(FIRST_BLOCK)
-                                + " but already set to " + obPhase.getFirstBlock() + " Duplicate phase file?");
-            }
-            addFirstBlock(obPhase, phaseConfig.getString(FIRST_BLOCK));
-        }
-
-        // Set icon
-        if (phaseConfig.contains(ICON)) {
-            ItemStack icon = ItemParser.parse(phaseConfig.getString(ICON));
-
-            if (icon == null) {
-                throw new IOException("ItemParser failed to parse icon: '" + phaseConfig.getString(ICON) + "' for phase "
-                        + obPhase.getFirstBlock() + ". Can you check if it is correct?");
-            }
-
-            obPhase.setIconBlock(icon);
-        }
-
-        // Add fixed blocks
-        if (phaseConfig.contains(FIXED_BLOCKS)) {
-            if (!obPhase.getFixedBlocks().isEmpty()) {
-                throw new IOException(
-                        "Block " + blockNumber + ": Fixed blocks trying to be set to " + phaseConfig.getString(FIXED_BLOCKS)
-                                + " but already set to " + obPhase.getFixedBlocks() + " Duplicate phase file?");
-            }
-            addFixedBlocks(obPhase, phaseConfig.getConfigurationSection(FIXED_BLOCKS));
-        }
-
-        // Add holograms
-        if (phaseConfig.contains(HOLOGRAMS)) {
-            if (!obPhase.getHologramLines().isEmpty()) {
-                throw new IOException(
-                        "Block " + blockNumber + ": Hologram Lines trying to be set to " + phaseConfig.getString(HOLOGRAMS)
-                                + " but already set to " + obPhase.getHologramLines() + " Duplicate phase file?");
-            }
-            addHologramLines(obPhase, phaseConfig.getConfigurationSection(HOLOGRAMS));
-        }
+	// Set name
+	if (phaseConfig.contains(NAME, true)) {
+	    if (obPhase.getPhaseName() != null) {
+		throw new IOException(
+			BLOCK + blockNumber + ": Phase name trying to be set to " + phaseConfig.getString(NAME)
+				+ BUT_ALREADY_SET_TO + obPhase.getPhaseName() + ". Duplicate phase file?");
+	    }
+	    obPhase.setPhaseName(phaseConfig.getString(NAME, blockNumber));
+	}
+
+	// Set biome
+	if (phaseConfig.contains(BIOME, true)) {
+	    if (obPhase.getPhaseBiome() != null) {
+		throw new IOException(BLOCK + blockNumber + ": Biome trying to be set to "
+			+ phaseConfig.getString(BIOME) + BUT_ALREADY_SET_TO + obPhase.getPhaseBiome() + DUPLICATE);
+	    }
+	    obPhase.setPhaseBiome(getBiome(phaseConfig.getString(BIOME)));
+	}
+
+	// Set first block
+	if (phaseConfig.contains(FIRST_BLOCK)) {
+	    if (obPhase.getFirstBlock() != null) {
+		throw new IOException(
+			BLOCK + blockNumber + ": First block trying to be set to " + phaseConfig.getString(FIRST_BLOCK)
+				+ BUT_ALREADY_SET_TO + obPhase.getFirstBlock() + DUPLICATE);
+	    }
+	    addFirstBlock(obPhase, phaseConfig.getString(FIRST_BLOCK));
+	}
+
+	// Set icon
+	if (phaseConfig.contains(ICON)) {
+	    ItemStack icon = ItemParser.parse(phaseConfig.getString(ICON));
+
+	    if (icon == null) {
+		throw new IOException("ItemParser failed to parse icon: '" + phaseConfig.getString(ICON)
+			+ "' for phase " + obPhase.getFirstBlock() + ". Can you check if it is correct?");
+	    }
+
+	    obPhase.setIconBlock(icon);
+	}
+
+	// Add fixed blocks
+	if (phaseConfig.contains(FIXED_BLOCKS)) {
+	    if (!obPhase.getFixedBlocks().isEmpty()) {
+		throw new IOException(BLOCK + blockNumber + ": Fixed blocks trying to be set to "
+			+ phaseConfig.getString(FIXED_BLOCKS) + BUT_ALREADY_SET_TO + obPhase.getFixedBlocks()
+			+ DUPLICATE);
+	    }
+	    addFixedBlocks(obPhase, phaseConfig.getConfigurationSection(FIXED_BLOCKS));
+	}
+
+	// Add holograms
+	if (phaseConfig.contains(HOLOGRAMS)) {
+	    if (!obPhase.getHologramLines().isEmpty()) {
+		throw new IOException(
+			BLOCK + blockNumber + ": Hologram Lines trying to be set to " + phaseConfig.getString(HOLOGRAMS)
+				+ BUT_ALREADY_SET_TO + obPhase.getHologramLines() + DUPLICATE);
+	    }
+	    addHologramLines(obPhase, phaseConfig.getConfigurationSection(HOLOGRAMS));
+	}
     }
 
     private void addFixedBlocks(OneBlockPhase obPhase, ConfigurationSection firstBlocksConfig) {
-        if (firstBlocksConfig == null) {
-            return;
-        }
-        Map result = new HashMap<>();
-        for (String key : firstBlocksConfig.getKeys(false)) {
-            if (!NumberUtils.isNumber(key)) {
-                addon.logError("Fixed block key must be an integer. " + key);
-                continue;
-            }
-            int k = Integer.parseInt(key);
-
-            if (firstBlocksConfig.isConfigurationSection(key)) { // Item value is object
-                Map map = firstBlocksConfig.getConfigurationSection(key).getValues(false);
-                Optional customBlock = OneBlockCustomBlockCreator.create(map);
-                if (customBlock.isPresent()) {
-                    result.put(k, new OneBlockObject(customBlock.get(), 0));
-                } else {
-                    addon.logError("Fixed block key " + key + " material is not a valid custom block. Ignoring.");
-                }
-            } else { // Item value is string
-                String mat = firstBlocksConfig.getString(key);
-                if (mat == null) {
-                    continue;
-                }
-
-                Optional customBlock = OneBlockCustomBlockCreator.create(mat);
-                if (customBlock.isPresent()) {
-                    result.put(k, new OneBlockObject(customBlock.get(), 0));
-                } else {
-                    Material m = Material.matchMaterial(mat);
-                    if (m != null && m.isBlock()) {
-                        result.put(k, new OneBlockObject(m, 0));
-                    } else {
-                        addon.logError("Fixed block key " + key + " material is invalid or not a block. Ignoring.");
-                    }
-                }
-            }
-        }
-        // Set the first block if it exists
-        if (result.containsKey(0)) {
-            addon.log("Found firstBlock in fixedBlocks.");
-            obPhase.setFirstBlock(result.get(0));
-        }
-        // Store the remainder
-        obPhase.setFixedBlocks(result);
-
-    }
-
-	private void addHologramLines(OneBlockPhase obPhase, ConfigurationSection fb) {
-		if (fb == null)
-			return;
-		Map result = new HashMap<>();
-		for (String key : fb.getKeys(false)) {
-			if (!NumberUtils.isNumber(key)) {
-				addon.logError("Fixed block key must be an integer. " + key);
-				continue;
-			}
-			int k = Integer.parseInt(key);
-			String line = fb.getString(key);
-			if (line != null) {
-				result.put(k, line);
-			}
-		}
-		// Set Hologram Lines
-		obPhase.setHologramLines(result);
-
+	if (firstBlocksConfig == null) {
+	    return;
 	}
 
-	private Biome getBiome(String string) {
-		if (string == null) {
-			return Biome.PLAINS;
-		}
-		if (Enums.getIfPresent(Biome.class, string).isPresent()) {
-			return Biome.valueOf(string);
-		}
-		// Special case for nether
-		if (string.equals("NETHER") || string.equals("NETHER_WASTES")) {
-			return Enums.getIfPresent(Biome.class, "NETHER")
-					.or(Enums.getIfPresent(Biome.class, "NETHER_WASTES").or(Biome.PLAINS));
-		}
-		addon.logError("Biome " + string.toUpperCase() + " is invalid! Use one of these...");
-		addon.logError(Arrays.stream(Biome.values()).map(Biome::name).collect(Collectors.joining(",")));
-		return Biome.PLAINS;
-	}
+	Map result = parseFirstBlocksConfig(firstBlocksConfig);
 
-	void addFirstBlock(OneBlockPhase obPhase, @Nullable String material) {
-		if (material == null) {
-			return;
-		}
-		Material m = Material.matchMaterial(material);
-		if (m == null || !m.isBlock()) {
-			addon.logError("Bad firstBlock material: " + material);
-		} else {
-			obPhase.setFirstBlock(new OneBlockObject(m, 0));
-		}
+	// Set the first block if it exists
+	if (result.containsKey(0)) {
+	    addon.log("Found firstBlock in fixedBlocks.");
+	    obPhase.setFirstBlock(result.get(0));
 	}
+	// Store the remainder
+	obPhase.setFixedBlocks(result);
+    }
 
-	void addCommands(OneBlockPhase obPhase, ConfigurationSection phase) {
-		if (phase.contains(START_COMMANDS)) {
-			obPhase.setStartCommands(phase.getStringList(START_COMMANDS));
-		}
-		if (phase.contains(END_COMMANDS)) {
-			obPhase.setEndCommands(phase.getStringList(END_COMMANDS));
-		}
-		if (phase.contains(END_COMMANDS_FIRST_TIME)) {
-			obPhase.setFirstTimeEndCommands(phase.getStringList(END_COMMANDS_FIRST_TIME));
-		}
-	}
+    private Map parseFirstBlocksConfig(ConfigurationSection firstBlocksConfig) {
+	Map result = new HashMap<>();
 
-	void addRequirements(OneBlockPhase obPhase, ConfigurationSection phase) {
-		List reqList = new ArrayList<>();
-		if (!phase.isConfigurationSection(REQUIREMENTS)) {
-			return;
-		}
-		ConfigurationSection reqs = phase.getConfigurationSection(REQUIREMENTS);
-		for (ReqType key : Requirement.ReqType.values()) {
-			if (reqs.contains(key.getKey())) {
-				Requirement r;
-				if (key.getClazz().equals(Double.class)) {
-					r = new Requirement(key, reqs.getDouble(key.getKey()));
-				} else if (key.getClazz().equals(Long.class)) {
-					r = new Requirement(key, reqs.getLong(key.getKey()));
-				} else {
-					r = new Requirement(key, reqs.getString(key.getKey()));
-				}
-				reqList.add(r);
-			}
-		}
-		obPhase.setRequirements(reqList);
+	for (String key : firstBlocksConfig.getKeys(false)) {
+	    if (!NumberUtils.isNumber(key)) {
+		addon.logError("Fixed block key must be an integer. " + key);
+		continue;
+	    }
+	    int k = Integer.parseInt(key);
+	    parseBlock(result, firstBlocksConfig, key, k);
 	}
+	return result;
+    }
 
-	void addChests(OneBlockPhase obPhase, ConfigurationSection phase) throws IOException {
-		if (!phase.isConfigurationSection(CHESTS)) {
-			return;
-		}
-		if (!obPhase.getChests().isEmpty()) {
-			throw new IOException(obPhase.getPhaseName() + ": Chests cannot be set more than once. Duplicate file?");
-		}
-		ConfigurationSection chests = phase.getConfigurationSection(CHESTS);
-		for (String chestId : chests.getKeys(false)) {
-			ConfigurationSection chest = chests.getConfigurationSection(chestId);
-			Rarity rarity = Rarity.COMMON;
-			try {
-				rarity = OneBlockObject.Rarity.valueOf(chest.getString(RARITY, "COMMON").toUpperCase());
-			} catch (Exception e) {
-				addon.logError(
-						"Rarity value of " + chest.getString(RARITY, "UNKNOWN") + " is invalid! Use one of these...");
-				addon.logError(Arrays.stream(Rarity.values()).map(Rarity::name).collect(Collectors.joining(",")));
-				rarity = Rarity.COMMON;
-			}
-			Map items = new HashMap<>();
-			ConfigurationSection contents = chest.getConfigurationSection(CONTENTS);
-			if (contents != null) {
-				for (String index : contents.getKeys(false)) {
-					int slot = Integer.parseInt(index);
-					ItemStack item = contents.getItemStack(index);
-					if (item != null) {
-						items.put(slot, item);
-					}
-				}
-			}
-			obPhase.addChest(items, rarity);
-		}
+    private void parseBlock(Map result, ConfigurationSection firstBlocksConfig, String key,
+	    int k) {
+	if (firstBlocksConfig.isConfigurationSection(key)) {
+	    parseObjectBlock(result, firstBlocksConfig, key, k);
+	} else {
+	    parseStringBlock(result, firstBlocksConfig, key, k);
 	}
+    }
 
-	void addMobs(OneBlockPhase obPhase, ConfigurationSection phase) throws IOException {
-		if (!phase.isConfigurationSection(MOBS)) {
-			return;
-		}
-		if (!obPhase.getMobs().isEmpty()) {
-			throw new IOException(obPhase.getPhaseName() + ": Mobs cannot be set more than once. Duplicate file?");
-		}
-		ConfigurationSection mobs = phase.getConfigurationSection(MOBS);
-		for (String entity : mobs.getKeys(false)) {
-			String name = entity.toUpperCase(Locale.ENGLISH);
-			EntityType et = null;
-			// Pig zombie handling
-			if (name.equals("PIG_ZOMBIE") || name.equals("ZOMBIFIED_PIGLIN")) {
-				et = Enums.getIfPresent(EntityType.class, "ZOMBIFIED_PIGLIN")
-						.or(Enums.getIfPresent(EntityType.class, "PIG_ZOMBIE").or(EntityType.PIG));
-			} else {
-				et = Enums.getIfPresent(EntityType.class, name).orNull();
-			}
-			if (et == null) {
-				// Does not exist
-				addon.logError("Bad entity type in " + obPhase.getPhaseName() + ": " + entity);
-				addon.logError("Try one of these...");
-				addon.logError(Arrays.stream(EntityType.values()).filter(EntityType::isSpawnable)
-						.filter(EntityType::isAlive).map(EntityType::name).collect(Collectors.joining(",")));
-				return;
-			}
-			if (et.isSpawnable() && et.isAlive()) {
-				if (mobs.getInt(entity) > 0) {
-					obPhase.addMob(et, mobs.getInt(entity));
-				} else {
-					addon.logWarning("Bad entity weight for " + obPhase.getPhaseName() + ": " + entity
-							+ ". Must be positive number above 1.");
-				}
-			} else {
-				addon.logError("Entity type is not spawnable " + obPhase.getPhaseName() + ": " + entity);
-			}
-		}
+    /**
+     * This method handles the case where the block's value is a configuration
+     * section, indicating that the block is defined as an object.
+     * 
+     * @param result            the resulting map
+     * @param firstBlocksConfig config
+     * @param key               key
+     * @param k                 integer value of key
+     */
+    private void parseObjectBlock(Map result, ConfigurationSection firstBlocksConfig,
+	    String key, int k) {
+	// Object block parsing logic
+	Map map = firstBlocksConfig.getConfigurationSection(key).getValues(false);
+	Optional customBlock = OneBlockCustomBlockCreator.create(map);
+	if (customBlock.isPresent()) {
+	    result.put(k, new OneBlockObject(customBlock.get(), 0));
+	} else {
+	    addon.logError("Fixed block key " + key + " material is not a valid custom block. Ignoring.");
 	}
+    }
 
-	void addBlocks(OneBlockPhase obPhase, ConfigurationSection phase) {
-		if (phase.isConfigurationSection(BLOCKS)) {
-			ConfigurationSection blocks = phase.getConfigurationSection(BLOCKS);
-			for (String material : blocks.getKeys(false)) {
-				if (Material.getMaterial(material) != null) {
-					addMaterial(obPhase, material, Objects.toString(blocks.get(material)));
-				} else {
-					if (addon.hasItemsAdder()) {
-						CustomBlock block = CustomBlock.getInstance(material);
-						if (block != null) {
-							addItemsAdderBlock(obPhase, material, Objects.toString(blocks.get(material)));
-						} else if (ItemsAdder.getAllItems() != null) {
-							if (ItemsAdder.getAllItems().size() != 0) {
-								addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material);
-							}
-						}
-					} else {
-						addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material);
-					}
-				}
-			}
-		} else if (phase.isList(BLOCKS)) {
-			List> blocks = phase.getMapList(BLOCKS);
-			for (Map map : blocks) {
-				if (map.size() == 1) {
-					Map.Entry entry = map.entrySet().iterator().next();
-					if (addMaterial(obPhase, Objects.toString(entry.getKey()), Objects.toString(entry.getValue()))) {
-						continue;
-					}
-				}
-
-				int probability = Integer.parseInt(Objects.toString(map.get("probability"), "0"));
-				Optional customBlock = OneBlockCustomBlockCreator.create(map);
-				if (customBlock.isPresent()) {
-					obPhase.addCustomBlock(customBlock.get(), probability);
-				} else {
-					addon.logError("Bad custom block in " + obPhase.getPhaseName() + ": " + map);
-				}
-			}
-		}
+    /**
+     * This method handles the case where the block's value is a string, which could
+     * either be a custom block or a standard Minecraft material.
+     * 
+     * @param result            the resulting map
+     * @param firstBlocksConfig config
+     * @param key               key
+     * @param k                 integer value of key
+     */
+    private void parseStringBlock(Map result, ConfigurationSection firstBlocksConfig,
+	    String key, int k) {
+	// String block parsing logic
+	String mat = firstBlocksConfig.getString(key);
+	if (mat == null) {
+	    return;
+	}
+
+	Optional customBlock = OneBlockCustomBlockCreator.create(mat);
+	if (customBlock.isPresent()) {
+	    result.put(k, new OneBlockObject(customBlock.get(), 0));
+	} else {
+	    Material m = Material.matchMaterial(mat);
+	    if (m != null && m.isBlock()) {
+		result.put(k, new OneBlockObject(m, 0));
+	    } else {
+		addon.logError("Fixed block key " + key + " material is invalid or not a block. Ignoring.");
+	    }
 	}
+    }
 
-	private boolean addMaterial(OneBlockPhase obPhase, String material, String probability) {
-		int prob;
-		try {
-			prob = Integer.parseInt(probability);
-		} catch (Exception e) {
-			return false;
-		}
+    private void addHologramLines(OneBlockPhase obPhase, ConfigurationSection fb) {
+	if (fb == null)
+	    return;
+	Map result = new HashMap<>();
+	for (String key : fb.getKeys(false)) {
+	    if (!NumberUtils.isNumber(key)) {
+		addon.logError("Fixed block key must be an integer. " + key);
+		continue;
+	    }
+	    int k = Integer.parseInt(key);
+	    String line = fb.getString(key);
+	    if (line != null) {
+		result.put(k, line);
+	    }
+	}
+	// Set Hologram Lines
+	obPhase.setHologramLines(result);
 
-		if (prob < 1) {
-			addon.logWarning("Bad item weight for " + obPhase.getPhaseName() + ": " + material
-					+ ". Must be positive number above 1.");
-			return false;
-		}
+    }
 
-		// Register if the material is a valid custom block and can be created from the
-		// short creator from OneBlockCustomBlockCreator
-		Optional optionalCustomBlock = OneBlockCustomBlockCreator.create(material);
-		if (optionalCustomBlock.isPresent()) {
-			obPhase.addCustomBlock(optionalCustomBlock.get(), prob);
-			return true;
-		}
+    private Biome getBiome(String string) {
+	if (string == null) {
+	    return Biome.PLAINS;
+	}
+	if (Enums.getIfPresent(Biome.class, string).isPresent()) {
+	    return Biome.valueOf(string);
+	}
+	// Special case for nether
+	if (string.equals("NETHER") || string.equals("NETHER_WASTES")) {
+	    return Enums.getIfPresent(Biome.class, "NETHER")
+		    .or(Enums.getIfPresent(Biome.class, "NETHER_WASTES").or(Biome.PLAINS));
+	}
+	addon.logError("Biome " + string.toUpperCase() + " is invalid! Use one of these...");
+	addon.logError(Arrays.stream(Biome.values()).map(Biome::name).collect(Collectors.joining(",")));
+	return Biome.PLAINS;
+    }
 
-		// Otherwise, register the material as a block
-		Material m = Material.matchMaterial(material);
-		if (m == null || !m.isBlock()) {
-			addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material);
-			return false;
-		}
-		obPhase.addBlock(m, prob);
-		return true;
-	}
-
-	private void addItemsAdderBlock(OneBlockPhase obPhase, String block, String probability) {
-		int prob;
-		try {
-			prob = Integer.parseInt(probability);
-			if (prob < 1) {
-				addon.logWarning("Bad item weight for " + obPhase.getPhaseName() + ": " + block
-						+ ". Must be positive number above 1.");
-			} else {
-				obPhase.addItemsAdderCustomBlock(block, prob);
-			}
-		} catch (Exception e) {
-			addon.logError("Bad item weight for " + obPhase.getPhaseName() + ": " + block + ". Must be a number.");
-		}
+    void addFirstBlock(OneBlockPhase obPhase, @Nullable String material) {
+	if (material == null) {
+	    return;
+	}
+	Material m = Material.matchMaterial(material);
+	if (m == null || !m.isBlock()) {
+	    addon.logError("Bad firstBlock material: " + material);
+	} else {
+	    obPhase.setFirstBlock(new OneBlockObject(m, 0));
+	}
+    }
 
+    void addCommands(OneBlockPhase obPhase, ConfigurationSection phase) {
+	if (phase.contains(START_COMMANDS)) {
+	    obPhase.setStartCommands(phase.getStringList(START_COMMANDS));
 	}
+	if (phase.contains(END_COMMANDS)) {
+	    obPhase.setEndCommands(phase.getStringList(END_COMMANDS));
+	}
+	if (phase.contains(END_COMMANDS_FIRST_TIME)) {
+	    obPhase.setFirstTimeEndCommands(phase.getStringList(END_COMMANDS_FIRST_TIME));
+	}
+    }
 
-	/**
-	 * Return the current phase for the block count
-	 *
-	 * @param blockCount - number of blocks mined
-	 * @return the one block phase based on blockCount or null if there is none
-	 */
-	@Nullable
-	public OneBlockPhase getPhase(int blockCount) {
-		Entry en = blockProbs.floorEntry(blockCount);
-		return en != null ? en.getValue() : null;
-	}
-
-	/**
-	 * @return list of phase names with spaces replaced by underscore so they are
-	 *         one word
-	 */
-	public List getPhaseList() {
-		return blockProbs.values().stream().map(OneBlockPhase::getPhaseName).filter(Objects::nonNull)
-				.map(n -> n.replace(" ", "_")).collect(Collectors.toList());
-	}
-
-	/**
-	 * @return the blockProbs
-	 */
-	public NavigableMap getBlockProbs() {
-		return blockProbs;
-	}
-
-	/**
-	 * Get phase by name. Name should have any spaces converted to underscores. Case
-	 * insensitive.
-	 *
-	 * @param name - name to search
-	 * @return optional OneBlockPhase
-	 */
-	public Optional getPhase(String name) {
-		return blockProbs.values().stream()
-				.filter(p -> p.getPhaseName() != null && (p.getPhaseName().equalsIgnoreCase(name)
-						|| p.getPhaseName().replace(" ", "_").equalsIgnoreCase(name)))
-				.findFirst();
-	}
-
-	/**
-	 * Save all the phases in memory to disk.
-	 *
-	 * @return true if saved
-	 */
-
-	public boolean saveOneBlockConfig() {
-		// Go through each phase
-		boolean success = true;
-		for (OneBlockPhase p : blockProbs.values()) {
-			success = savePhase(p);
-		}
-		return success;
-	}
-
-	/**
-	 * Save a phase
-	 * 
-	 * @param p OneBlockPhase
-	 * @return true if successfully saved
-	 */
-	public boolean savePhase(OneBlockPhase p) {
-		if (!saveMainPhase(p)) {
-			// Failure
-			return false;
+    void addRequirements(OneBlockPhase obPhase, ConfigurationSection phase) {
+	List reqList = new ArrayList<>();
+	if (!phase.isConfigurationSection(REQUIREMENTS)) {
+	    return;
+	}
+	ConfigurationSection reqs = phase.getConfigurationSection(REQUIREMENTS);
+	for (ReqType key : Requirement.ReqType.values()) {
+	    if (reqs.contains(key.getKey())) {
+		Requirement r;
+		if (key.getClazz().equals(Double.class)) {
+		    r = new Requirement(key, reqs.getDouble(key.getKey()));
+		} else if (key.getClazz().equals(Long.class)) {
+		    r = new Requirement(key, reqs.getLong(key.getKey()));
+		} else {
+		    r = new Requirement(key, reqs.getString(key.getKey()));
 		}
-		// No chests in goto phases
-		if (p.isGotoPhase()) {
-			// Done
-			return true;
+		reqList.add(r);
+	    }
+	}
+	obPhase.setRequirements(reqList);
+    }
+
+    void addChests(OneBlockPhase obPhase, ConfigurationSection phase) throws IOException {
+	if (!phase.isConfigurationSection(CHESTS)) {
+	    return;
+	}
+	if (!obPhase.getChests().isEmpty()) {
+	    throw new IOException(obPhase.getPhaseName() + ": Chests cannot be set more than once. Duplicate file?");
+	}
+	ConfigurationSection chests = phase.getConfigurationSection(CHESTS);
+	for (String chestId : chests.getKeys(false)) {
+	    ConfigurationSection chest = chests.getConfigurationSection(chestId);
+	    Rarity rarity = Rarity.COMMON;
+	    try {
+		rarity = OneBlockObject.Rarity.valueOf(chest.getString(RARITY, "COMMON").toUpperCase());
+	    } catch (Exception e) {
+		addon.logError(
+			"Rarity value of " + chest.getString(RARITY, "UNKNOWN") + " is invalid! Use one of these...");
+		addon.logError(Arrays.stream(Rarity.values()).map(Rarity::name).collect(Collectors.joining(",")));
+		rarity = Rarity.COMMON;
+	    }
+	    Map items = new HashMap<>();
+	    ConfigurationSection contents = chest.getConfigurationSection(CONTENTS);
+	    if (contents != null) {
+		for (String index : contents.getKeys(false)) {
+		    int slot = Integer.parseInt(index);
+		    ItemStack item = contents.getItemStack(index);
+		    if (item != null) {
+			items.put(slot, item);
+		    }
+		}
+	    }
+	    obPhase.addChest(items, rarity);
+	}
+    }
+
+    void addMobs(OneBlockPhase obPhase, ConfigurationSection phase) throws IOException {
+	if (!phase.isConfigurationSection(MOBS)) {
+	    return;
+	}
+	if (!obPhase.getMobs().isEmpty()) {
+	    throw new IOException(obPhase.getPhaseName() + ": Mobs cannot be set more than once. Duplicate file?");
+	}
+	ConfigurationSection mobs = phase.getConfigurationSection(MOBS);
+	for (String entity : mobs.getKeys(false)) {
+	    String name = entity.toUpperCase(Locale.ENGLISH);
+	    EntityType et = null;
+	    // Pig zombie handling
+	    if (name.equals("PIG_ZOMBIE") || name.equals("ZOMBIFIED_PIGLIN")) {
+		et = Enums.getIfPresent(EntityType.class, "ZOMBIFIED_PIGLIN")
+			.or(Enums.getIfPresent(EntityType.class, "PIG_ZOMBIE").or(EntityType.PIG));
+	    } else {
+		et = Enums.getIfPresent(EntityType.class, name).orNull();
+	    }
+	    if (et == null) {
+		// Does not exist
+		addon.logError("Bad entity type in " + obPhase.getPhaseName() + ": " + entity);
+		addon.logError("Try one of these...");
+		addon.logError(Arrays.stream(EntityType.values()).filter(EntityType::isSpawnable)
+			.filter(EntityType::isAlive).map(EntityType::name).collect(Collectors.joining(",")));
+		return;
+	    }
+	    if (et.isSpawnable() && et.isAlive()) {
+		if (mobs.getInt(entity) > 0) {
+		    obPhase.addMob(et, mobs.getInt(entity));
+		} else {
+		    addon.logWarning("Bad entity weight for " + obPhase.getPhaseName() + ": " + entity
+			    + ". Must be positive number above 1.");
 		}
-		return saveChestPhase(p);
+	    } else {
+		addon.logError("Entity type is not spawnable " + obPhase.getPhaseName() + ": " + entity);
+	    }
 	}
+    }
 
-	private boolean saveMainPhase(OneBlockPhase p) {
-		YamlConfiguration oneBlocks = new YamlConfiguration();
-		ConfigurationSection phSec = oneBlocks.createSection(p.getBlockNumber());
-		// Check for a goto block
-		if (p.isGotoPhase()) {
-			phSec.set(GOTO_BLOCK, p.getGotoBlock());
+    void addBlocks(OneBlockPhase obPhase, ConfigurationSection phase) {
+	if (phase.isConfigurationSection(BLOCKS)) {
+	    ConfigurationSection blocks = phase.getConfigurationSection(BLOCKS);
+	    for (String material : blocks.getKeys(false)) {
+		if (Material.getMaterial(material) != null) {
+		    addMaterial(obPhase, material, Objects.toString(blocks.get(material)));
 		} else {
-			phSec.set(NAME, p.getPhaseName());
-			if (p.getIconBlock() != null) {
-				phSec.set(ICON, p.getIconBlock().getType().name());
-			}
-			if (p.getFirstBlock() != null) {
-				phSec.set(FIRST_BLOCK, p.getFirstBlock().getMaterial().name());
-			}
-			if (p.getPhaseBiome() != null) {
-				phSec.set(BIOME, p.getPhaseBiome().name());
+		    if (addon.hasItemsAdder()) {
+			CustomBlock block = CustomBlock.getInstance(material);
+			if (block != null) {
+			    addItemsAdderBlock(obPhase, material, Objects.toString(blocks.get(material)));
+			} else if (ItemsAdder.getAllItems() != null) {
+			    if (ItemsAdder.getAllItems().size() != 0) {
+				addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material);
+			    }
 			}
-			saveBlocks(phSec, p);
-			saveEntities(phSec, p);
-			saveHolos(phSec, p);
-			saveCommands(phSec, p);
-		}
-		try {
-			// Save
-			File phaseFile = new File(addon.getDataFolder() + File.separator + PHASES, getPhaseFileName(p) + ".yml");
-			oneBlocks.save(phaseFile);
-		} catch (IOException e) {
-			addon.logError("Could not save phase " + p.getPhaseName() + " " + e.getMessage());
-			return false;
+		    } else {
+			addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material);
+		    }
+		}
+	    }
+	} else if (phase.isList(BLOCKS)) {
+	    List> blocks = phase.getMapList(BLOCKS);
+	    for (Map map : blocks) {
+		if (map.size() == 1) {
+		    Map.Entry entry = map.entrySet().iterator().next();
+		    if (addMaterial(obPhase, Objects.toString(entry.getKey()), Objects.toString(entry.getValue()))) {
+			continue;
+		    }
+		}
+
+		int probability = Integer.parseInt(Objects.toString(map.get("probability"), "0"));
+		Optional customBlock = OneBlockCustomBlockCreator.create(map);
+		if (customBlock.isPresent()) {
+		    obPhase.addCustomBlock(customBlock.get(), probability);
+		} else {
+		    addon.logError("Bad custom block in " + obPhase.getPhaseName() + ": " + map);
 		}
-		return true;
+	    }
 	}
+    }
 
-	private void saveCommands(ConfigurationSection phSec, OneBlockPhase p) {
-		phSec.set(START_COMMANDS, p.getStartCommands());
-		phSec.set(END_COMMANDS, p.getEndCommands());
+    private boolean addMaterial(OneBlockPhase obPhase, String material, String probability) {
+	int prob;
+	try {
+	    prob = Integer.parseInt(probability);
+	} catch (Exception e) {
+	    return false;
+	}
 
+	if (prob < 1) {
+	    addon.logWarning("Bad item weight for " + obPhase.getPhaseName() + ": " + material
+		    + ". Must be positive number above 1.");
+	    return false;
 	}
 
-	private void saveHolos(ConfigurationSection phSec, OneBlockPhase p) {
-		if (p.getHologramLines() == null)
-			return;
-		ConfigurationSection holos = phSec.createSection(HOLOGRAMS);
-		p.getHologramLines().forEach((k, v) -> holos.set(String.valueOf(k), v));
+	// Register if the material is a valid custom block and can be created from the
+	// short creator from OneBlockCustomBlockCreator
+	Optional optionalCustomBlock = OneBlockCustomBlockCreator.create(material);
+	if (optionalCustomBlock.isPresent()) {
+	    obPhase.addCustomBlock(optionalCustomBlock.get(), prob);
+	    return true;
 	}
 
-	private boolean saveChestPhase(OneBlockPhase p) {
-		YamlConfiguration oneBlocks = new YamlConfiguration();
-		ConfigurationSection phSec = oneBlocks.createSection(p.getBlockNumber());
-		saveChests(phSec, p);
-		try {
-			// Save
-			File phaseFile = new File(addon.getDataFolder() + File.separator + PHASES,
-					getPhaseFileName(p) + "_chests.yml");
-			oneBlocks.save(phaseFile);
-		} catch (IOException e) {
-			addon.logError("Could not save chest phase " + p.getPhaseName() + " " + e.getMessage());
-			return false;
-		}
-		return true;
+	// Otherwise, register the material as a block
+	Material m = Material.matchMaterial(material);
+	if (m == null || !m.isBlock()) {
+	    addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material);
+	    return false;
 	}
+	obPhase.addBlock(m, prob);
+	return true;
+    }
 
-	private String getPhaseFileName(OneBlockPhase p) {
-		if (p.isGotoPhase()) {
-			return p.getBlockNumber() + "_goto_" + p.getGotoBlock();
-		}
-		return p.getBlockNumber() + "_"
-				+ (p.getPhaseName() == null ? "" : p.getPhaseName().toLowerCase().replace(' ', '_'));
+    private void addItemsAdderBlock(OneBlockPhase obPhase, String block, String probability) {
+	int prob;
+	try {
+	    prob = Integer.parseInt(probability);
+	    if (prob < 1) {
+		addon.logWarning("Bad item weight for " + obPhase.getPhaseName() + ": " + block
+			+ ". Must be positive number above 1.");
+	    } else {
+		obPhase.addItemsAdderCustomBlock(block, prob);
+	    }
+	} catch (Exception e) {
+	    addon.logError("Bad item weight for " + obPhase.getPhaseName() + ": " + block + ". Must be a number.");
 	}
 
-	private void saveChests(ConfigurationSection phSec, OneBlockPhase phase) {
-		ConfigurationSection chests = phSec.createSection(CHESTS);
-		int index = 1;
-		for (OneBlockObject chest : phase.getChests()) {
-			ConfigurationSection c = chests.createSection(String.valueOf(index++));
-			c.set(CONTENTS, chest.getChest());
-			c.set(RARITY, chest.getRarity().name());
-		}
+    }
+
+    /**
+     * Return the current phase for the block count
+     *
+     * @param blockCount - number of blocks mined
+     * @return the one block phase based on blockCount or null if there is none
+     */
+    @Nullable
+    public OneBlockPhase getPhase(int blockCount) {
+	Entry en = blockProbs.floorEntry(blockCount);
+	return en != null ? en.getValue() : null;
+    }
+
+    /**
+     * @return list of phase names with spaces replaced by underscore so they are
+     *         one word
+     */
+    public List getPhaseList() {
+	return blockProbs.values().stream().map(OneBlockPhase::getPhaseName).filter(Objects::nonNull)
+		.map(n -> n.replace(" ", "_")).collect(Collectors.toList());
+    }
+
+    /**
+     * @return the blockProbs
+     */
+    public NavigableMap getBlockProbs() {
+	return blockProbs;
+    }
+
+    /**
+     * Get phase by name. Name should have any spaces converted to underscores. Case
+     * insensitive.
+     *
+     * @param name - name to search
+     * @return optional OneBlockPhase
+     */
+    public Optional getPhase(String name) {
+	return blockProbs.values().stream()
+		.filter(p -> p.getPhaseName() != null && (p.getPhaseName().equalsIgnoreCase(name)
+			|| p.getPhaseName().replace(" ", "_").equalsIgnoreCase(name)))
+		.findFirst();
+    }
+
+    /**
+     * Save all the phases in memory to disk.
+     *
+     * @return true if saved
+     */
 
+    public boolean saveOneBlockConfig() {
+	// Go through each phase
+	boolean success = true;
+	for (OneBlockPhase p : blockProbs.values()) {
+	    success = savePhase(p);
 	}
+	return success;
+    }
 
-	private void saveEntities(ConfigurationSection phSec, OneBlockPhase phase) {
-		ConfigurationSection mobs = phSec.createSection(MOBS);
-		phase.getMobs().forEach((k, v) -> mobs.set(k.name(), v));
+    /**
+     * Save a phase
+     * 
+     * @param p OneBlockPhase
+     * @return true if successfully saved
+     */
+    public boolean savePhase(OneBlockPhase p) {
+	if (!saveMainPhase(p)) {
+	    // Failure
+	    return false;
+	}
+	// No chests in goto phases
+	if (p.isGotoPhase()) {
+	    // Done
+	    return true;
 	}
+	return saveChestPhase(p);
+    }
 
-	private void saveBlocks(ConfigurationSection phSec, OneBlockPhase phase) {
-		ConfigurationSection fixedBlocks = phSec.createSection(FIXED_BLOCKS);
-		phase.getFixedBlocks().forEach((k, v) -> fixedBlocks.set(String.valueOf(k), v.getMaterial().name()));
-		ConfigurationSection blocks = phSec.createSection(BLOCKS);
-		phase.getBlocks().forEach((k, v) -> blocks.set(k.name(), v));
+    private boolean saveMainPhase(OneBlockPhase p) {
+	YamlConfiguration oneBlocks = new YamlConfiguration();
+	ConfigurationSection phSec = oneBlocks.createSection(p.getBlockNumber());
+	// Check for a goto block
+	if (p.isGotoPhase()) {
+	    phSec.set(GOTO_BLOCK, p.getGotoBlock());
+	} else {
+	    phSec.set(NAME, p.getPhaseName());
+	    if (p.getIconBlock() != null) {
+		phSec.set(ICON, p.getIconBlock().getType().name());
+	    }
+	    if (p.getFirstBlock() != null) {
+		phSec.set(FIRST_BLOCK, p.getFirstBlock().getMaterial().name());
+	    }
+	    if (p.getPhaseBiome() != null) {
+		phSec.set(BIOME, p.getPhaseBiome().name());
+	    }
+	    saveBlocks(phSec, p);
+	    saveEntities(phSec, p);
+	    saveHolos(phSec, p);
+	    saveCommands(phSec, p);
+	}
+	try {
+	    // Save
+	    File phaseFile = new File(addon.getDataFolder() + File.separator + PHASES, getPhaseFileName(p) + ".yml");
+	    oneBlocks.save(phaseFile);
+	} catch (IOException e) {
+	    addon.logError("Could not save phase " + p.getPhaseName() + " " + e.getMessage());
+	    return false;
+	}
+	return true;
+    }
+
+    private void saveCommands(ConfigurationSection phSec, OneBlockPhase p) {
+	phSec.set(START_COMMANDS, p.getStartCommands());
+	phSec.set(END_COMMANDS, p.getEndCommands());
+
+    }
+
+    private void saveHolos(ConfigurationSection phSec, OneBlockPhase p) {
+	if (p.getHologramLines() == null)
+	    return;
+	ConfigurationSection holos = phSec.createSection(HOLOGRAMS);
+	p.getHologramLines().forEach((k, v) -> holos.set(String.valueOf(k), v));
+    }
 
+    private boolean saveChestPhase(OneBlockPhase p) {
+	YamlConfiguration oneBlocks = new YamlConfiguration();
+	ConfigurationSection phSec = oneBlocks.createSection(p.getBlockNumber());
+	saveChests(phSec, p);
+	try {
+	    // Save
+	    File phaseFile = new File(addon.getDataFolder() + File.separator + PHASES,
+		    getPhaseFileName(p) + "_chests.yml");
+	    oneBlocks.save(phaseFile);
+	} catch (IOException e) {
+	    addon.logError("Could not save chest phase " + p.getPhaseName() + " " + e.getMessage());
+	    return false;
+	}
+	return true;
+    }
+
+    private String getPhaseFileName(OneBlockPhase p) {
+	if (p.isGotoPhase()) {
+	    return p.getBlockNumber() + "_goto_" + p.getGotoBlock();
 	}
+	return p.getBlockNumber() + "_"
+		+ (p.getPhaseName() == null ? "" : p.getPhaseName().toLowerCase().replace(' ', '_'));
+    }
 
-	/**
-	 * Get the phase after this one
-	 *
-	 * @param phase - one block phase
-	 * @return next phase or null if there isn't one
-	 */
-	@SuppressWarnings("WrapperTypeMayBePrimitive")
-	@Nullable
-	public OneBlockPhase getNextPhase(@NonNull OneBlockPhase phase) {
-		// These are Integer objects because GSON can yield nulls if they do not exist
-		Integer blockNum = phase.getBlockNumberValue();
-		Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
-		return nextKey != null ? this.getPhase(nextKey) : null;
+    private void saveChests(ConfigurationSection phSec, OneBlockPhase phase) {
+	ConfigurationSection chests = phSec.createSection(CHESTS);
+	int index = 1;
+	for (OneBlockObject chest : phase.getChests()) {
+	    ConfigurationSection c = chests.createSection(String.valueOf(index++));
+	    c.set(CONTENTS, chest.getChest());
+	    c.set(RARITY, chest.getRarity().name());
 	}
 
-	/**
-	 * Get the number of blocks until the next phase after this one
-	 *
-	 * @param obi - one block island
-	 * @return number of blocks to the next phase. If there is no phase after -1 is
-	 *         returned.
-	 */
-	public int getNextPhaseBlocks(@NonNull OneBlockIslands obi) {
-		Integer blockNum = obi.getBlockNumber();
-		Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
-		if (nextKey == null) {
-			return -1;
-		}
-		OneBlockPhase nextPhase = this.getPhase(nextKey);
-		return nextPhase == null ? -1 : (nextPhase.getBlockNumberValue() - obi.getBlockNumber());
-	}
-
-	/**
-	 * Get the number of blocks for this phase
-	 *
-	 * @param obi - one block island
-	 * @return number of blocks for this current phase. If there is no phase after
-	 *         -1 is returned.
-	 */
-	public int getPhaseBlocks(@NonNull OneBlockIslands obi) {
-		Integer blockNum = obi.getBlockNumber();
-		Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
-		if (nextKey == null) {
-			return -1;
-		}
-		OneBlockPhase thisPhase = this.getPhase(blockNum);
-		if (thisPhase == null)
-			return -1;
-		OneBlockPhase nextPhase = this.getPhase(nextKey);
-		return nextPhase == null ? -1 : (nextPhase.getBlockNumberValue() - thisPhase.getBlockNumberValue());
-	}
-
-	/**
-	 * Get the percentage done of this phase
-	 *
-	 * @param obi - one block island
-	 * @return percentage done. If there is no next phase then return 0
-	 */
-	public double getPercentageDone(@NonNull OneBlockIslands obi) {
-		int blockNum = obi.getBlockNumber();
-		OneBlockPhase thisPhase = this.getPhase(blockNum);
-		if (thisPhase == null) {
-			return 0;
-		}
-		Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
-		if (nextKey == null) {
-			return 0;
-		}
-		OneBlockPhase nextPhase = this.getPhase(nextKey);
-		if (nextPhase == null) {
-			return 0;
-		}
-		int phaseSize = nextPhase.getBlockNumberValue() - thisPhase.getBlockNumberValue();
-		return 100 - (100 * (double) (nextPhase.getBlockNumberValue() - obi.getBlockNumber()) / phaseSize);
-	}
-
-	public void getProbs(OneBlockPhase phase) {
-		// Find the phase after this one
-		Integer blockNum = Integer.valueOf(phase.getBlockNumber());
-		Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
-		if (nextKey != null) {
-			// This is the size of the phase in blocks
-			int phaseSize = nextKey - blockNum;
-			int blockTotal = phase.getBlockTotal();
-			int likelyChestTotal = 0;
-			double totalBlocks = 0;
-			// Now calculate the relative block probability
-			for (Entry en : phase.getBlocks().entrySet()) {
-				double chance = (double) en.getValue() / blockTotal;
-				double likelyNumberGenerated = chance * phaseSize;
-				totalBlocks += likelyNumberGenerated;
-				String report = en.getKey() + " likely generated = " + Math.round(likelyNumberGenerated) + " = "
-						+ Math.round(likelyNumberGenerated * 100 / phaseSize) + "%";
-				if (likelyNumberGenerated < 1) {
-					addon.logWarning(report);
-				} else {
-					addon.log(report);
-				}
-				if (en.getKey().equals(Material.CHEST)) {
-					likelyChestTotal = (int) Math.round(likelyNumberGenerated);
-				}
-			}
-			addon.log("Total blocks generated = " + totalBlocks);
-			// Get the specific chest probability
-			if (likelyChestTotal == 0) {
-				addon.logWarning("No chests will be generated");
-				return;
-			}
-			addon.log("**** A total of " + likelyChestTotal + " chests will be generated ****");
-			// Now calculate chest chances
-			double lastChance = 0;
-			for (Entry en : OneBlockPhase.CHEST_CHANCES.entrySet()) {
-				// Get the number of chests in this rarity group
-				int num = phase.getChestsMap().getOrDefault(en.getValue(), Collections.emptyList()).size();
-				double likelyNumberGenerated = (en.getKey() - lastChance) * likelyChestTotal;
-				lastChance = en.getKey();
-				String report = num + " " + en.getValue() + " chests in phase. Likely number generated = "
-						+ Math.round(likelyNumberGenerated);
-				if (num > 0 && likelyNumberGenerated < 1) {
-					addon.logWarning(report);
-				} else {
-					addon.log(report);
-				}
+    }
 
-			}
-			// Mobs
-			addon.log("-=-=-=-= Mobs -=-=-=-=-");
-			double totalMobs = 0;
-			// Now calculate the relative block probability
-			for (Entry en : phase.getMobs().entrySet()) {
-				double chance = (double) en.getValue() / phase.getTotal();
-				double likelyNumberGenerated = chance * phaseSize;
-				totalMobs += likelyNumberGenerated;
-				String report = en.getKey() + " likely generated = " + Math.round(likelyNumberGenerated) + " = "
-						+ Math.round(likelyNumberGenerated * 100 / phaseSize) + "%";
-				if (likelyNumberGenerated < 1) {
-					addon.logWarning(report);
-				} else {
-					addon.log(report);
-				}
-			}
-			addon.log("**** A total of " + Math.round(totalMobs) + " mobs will likely be generated ****");
+    private void saveEntities(ConfigurationSection phSec, OneBlockPhase phase) {
+	ConfigurationSection mobs = phSec.createSection(MOBS);
+	phase.getMobs().forEach((k, v) -> mobs.set(k.name(), v));
+    }
+
+    private void saveBlocks(ConfigurationSection phSec, OneBlockPhase phase) {
+	ConfigurationSection fixedBlocks = phSec.createSection(FIXED_BLOCKS);
+	phase.getFixedBlocks().forEach((k, v) -> fixedBlocks.set(String.valueOf(k), v.getMaterial().name()));
+	ConfigurationSection blocks = phSec.createSection(BLOCKS);
+	phase.getBlocks().forEach((k, v) -> blocks.set(k.name(), v));
+
+    }
+
+    /**
+     * Get the phase after this one
+     *
+     * @param phase - one block phase
+     * @return next phase or null if there isn't one
+     */
+    @SuppressWarnings("WrapperTypeMayBePrimitive")
+    @Nullable
+    public OneBlockPhase getNextPhase(@NonNull OneBlockPhase phase) {
+	// These are Integer objects because GSON can yield nulls if they do not exist
+	Integer blockNum = phase.getBlockNumberValue();
+	Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
+	return nextKey != null ? this.getPhase(nextKey) : null;
+    }
+
+    /**
+     * Get the number of blocks until the next phase after this one
+     *
+     * @param obi - one block island
+     * @return number of blocks to the next phase. If there is no phase after -1 is
+     *         returned.
+     */
+    public int getNextPhaseBlocks(@NonNull OneBlockIslands obi) {
+	Integer blockNum = obi.getBlockNumber();
+	Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
+	if (nextKey == null) {
+	    return -1;
+	}
+	OneBlockPhase nextPhase = this.getPhase(nextKey);
+	return nextPhase == null ? -1 : (nextPhase.getBlockNumberValue() - obi.getBlockNumber());
+    }
+
+    /**
+     * Get the number of blocks for this phase
+     *
+     * @param obi - one block island
+     * @return number of blocks for this current phase. If there is no phase after
+     *         -1 is returned.
+     */
+    public int getPhaseBlocks(@NonNull OneBlockIslands obi) {
+	Integer blockNum = obi.getBlockNumber();
+	Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
+	if (nextKey == null) {
+	    return -1;
+	}
+	OneBlockPhase thisPhase = this.getPhase(blockNum);
+	if (thisPhase == null)
+	    return -1;
+	OneBlockPhase nextPhase = this.getPhase(nextKey);
+	return nextPhase == null ? -1 : (nextPhase.getBlockNumberValue() - thisPhase.getBlockNumberValue());
+    }
+
+    /**
+     * Get the percentage done of this phase
+     *
+     * @param obi - one block island
+     * @return percentage done. If there is no next phase then return 0
+     */
+    public double getPercentageDone(@NonNull OneBlockIslands obi) {
+	int blockNum = obi.getBlockNumber();
+	OneBlockPhase thisPhase = this.getPhase(blockNum);
+	if (thisPhase == null) {
+	    return 0;
+	}
+	Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
+	if (nextKey == null) {
+	    return 0;
+	}
+	OneBlockPhase nextPhase = this.getPhase(nextKey);
+	if (nextPhase == null) {
+	    return 0;
+	}
+	int phaseSize = nextPhase.getBlockNumberValue() - thisPhase.getBlockNumberValue();
+	return 100 - (100 * (double) (nextPhase.getBlockNumberValue() - obi.getBlockNumber()) / phaseSize);
+    }
+
+    public void getProbs(OneBlockPhase phase) {
+	// Find the phase after this one
+	Integer blockNum = Integer.valueOf(phase.getBlockNumber());
+	Integer nextKey = blockProbs.ceilingKey(blockNum + 1);
+	if (nextKey != null) {
+	    // This is the size of the phase in blocks
+	    int phaseSize = nextKey - blockNum;
+	    int blockTotal = phase.getBlockTotal();
+	    int likelyChestTotal = 0;
+	    double totalBlocks = 0;
+	    // Now calculate the relative block probability
+	    for (Entry en : phase.getBlocks().entrySet()) {
+		double chance = (double) en.getValue() / blockTotal;
+		double likelyNumberGenerated = chance * phaseSize;
+		totalBlocks += likelyNumberGenerated;
+		String report = en.getKey() + " likely generated = " + Math.round(likelyNumberGenerated) + " = "
+			+ Math.round(likelyNumberGenerated * 100 / phaseSize) + "%";
+		if (likelyNumberGenerated < 1) {
+		    addon.logWarning(report);
+		} else {
+		    addon.log(report);
+		}
+		if (en.getKey().equals(Material.CHEST)) {
+		    likelyChestTotal = (int) Math.round(likelyNumberGenerated);
+		}
+	    }
+	    addon.log("Total blocks generated = " + totalBlocks);
+	    // Get the specific chest probability
+	    if (likelyChestTotal == 0) {
+		addon.logWarning("No chests will be generated");
+		return;
+	    }
+	    addon.log("**** A total of " + likelyChestTotal + " chests will be generated ****");
+	    // Now calculate chest chances
+	    double lastChance = 0;
+	    for (Entry en : OneBlockPhase.CHEST_CHANCES.entrySet()) {
+		// Get the number of chests in this rarity group
+		int num = phase.getChestsMap().getOrDefault(en.getValue(), Collections.emptyList()).size();
+		double likelyNumberGenerated = (en.getKey() - lastChance) * likelyChestTotal;
+		lastChance = en.getKey();
+		String report = num + " " + en.getValue() + " chests in phase. Likely number generated = "
+			+ Math.round(likelyNumberGenerated);
+		if (num > 0 && likelyNumberGenerated < 1) {
+		    addon.logWarning(report);
+		} else {
+		    addon.log(report);
+		}
+
+	    }
+	    // Mobs
+	    addon.log("-=-=-=-= Mobs -=-=-=-=-");
+	    double totalMobs = 0;
+	    // Now calculate the relative block probability
+	    for (Entry en : phase.getMobs().entrySet()) {
+		double chance = (double) en.getValue() / phase.getTotal();
+		double likelyNumberGenerated = chance * phaseSize;
+		totalMobs += likelyNumberGenerated;
+		String report = en.getKey() + " likely generated = " + Math.round(likelyNumberGenerated) + " = "
+			+ Math.round(likelyNumberGenerated * 100 / phaseSize) + "%";
+		if (likelyNumberGenerated < 1) {
+		    addon.logWarning(report);
+		} else {
+		    addon.log(report);
 		}
+	    }
+	    addon.log("**** A total of " + Math.round(totalMobs) + " mobs will likely be generated ****");
 	}
+    }
 
-	/**
-	 * Get all the probs for each phases and log to console
-	 */
-	public void getAllProbs() {
-		blockProbs.values().forEach(this::getProbs);
-	}
+    /**
+     * Get all the probs for each phases and log to console
+     */
+    public void getAllProbs() {
+	blockProbs.values().forEach(this::getProbs);
+    }
 
-	/**
-	 * Get the next phase name
-	 *
-	 * @param obi - one block island
-	 * @return next phase name or an empty string
-	 */
-	public String getNextPhase(@NonNull OneBlockIslands obi) {
-		return getPhase(obi.getPhaseName()).map(this::getNextPhase) // Next phase or null
-				.filter(Objects::nonNull).map(OneBlockPhase::getPhaseName).orElse("");
-	}
+    /**
+     * Get the next phase name
+     *
+     * @param obi - one block island
+     * @return next phase name or an empty string
+     */
+    public String getNextPhase(@NonNull OneBlockIslands obi) {
+	return getPhase(obi.getPhaseName()).map(this::getNextPhase) // Next phase or null
+		.filter(Objects::nonNull).map(OneBlockPhase::getPhaseName).orElse("");
+    }
 }

From 019817027dbdc28bdcc566820a831df8415bad25 Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Sat, 25 Nov 2023 13:13:34 -0800
Subject: [PATCH 36/42] Refactoring

---
 .../aoneblock/listeners/CheckPhase.java       | 45 +++++++++----------
 1 file changed, 22 insertions(+), 23 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java
index 0f3dc478..6423a345 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/CheckPhase.java
@@ -108,24 +108,25 @@ protected boolean phaseRequirementsFail(@Nullable Player player, @NonNull Island
 	    return false;
 	}
 
-	return phase.getRequirements().stream().anyMatch(r -> checkRequirement(r, player, i, is, world));
+	return phase.getRequirements().stream()
+		.anyMatch(r -> checkRequirement(r, User.getInstance(player), i, is, world));
     }
 
-    private boolean checkRequirement(Requirement r, Player player, Island i, OneBlockIslands is, World world) {
+    private boolean checkRequirement(Requirement r, User user, Island i, OneBlockIslands is, World world) {
 	return switch (r.getType()) {
-	case LEVEL -> checkLevelRequirement(r, player, i, world);
-	case BANK -> checkBankRequirement(r, player, i, world);
-	case ECO -> checkEcoRequirement(r, player, world);
-	case PERMISSION -> checkPermissionRequirement(r, player);
-	case COOLDOWN -> checkCooldownRequirement(r, player, is);
+	case LEVEL -> checkLevelRequirement(r, user, i, world);
+	case BANK -> checkBankRequirement(r, user, i);
+	case ECO -> checkEcoRequirement(r, user, world);
+	case PERMISSION -> checkPermissionRequirement(r, user);
+	case COOLDOWN -> checkCooldownRequirement(r, user, is);
 	};
     }
 
-    private boolean checkLevelRequirement(Requirement r, Player player, Island i, World world) {
+    private boolean checkLevelRequirement(Requirement r, User user, Island i, World world) {
 	// Level checking logic
 	return addon.getAddonByName("Level").map(l -> {
 	    if (((Level) l).getIslandLevel(world, i.getOwner()) < r.getLevel()) {
-		User.getInstance(player).sendMessage("aoneblock.phase.insufficient-level", TextVariables.NUMBER,
+		user.sendMessage("aoneblock.phase.insufficient-level", TextVariables.NUMBER,
 			String.valueOf(r.getLevel()));
 		return true;
 	    }
@@ -133,11 +134,11 @@ private boolean checkLevelRequirement(Requirement r, Player player, Island i, Wo
 	}).orElse(false);
     }
 
-    private boolean checkBankRequirement(Requirement r, Player player, Island i, World world) {
+    private boolean checkBankRequirement(Requirement r, User user, Island i) {
 	// Bank checking logic
 	return addon.getAddonByName("Bank").map(l -> {
 	    if (((Bank) l).getBankManager().getBalance(i).getValue() < r.getBank()) {
-		User.getInstance(player).sendMessage("aoneblock.phase.insufficient-bank-balance", TextVariables.NUMBER,
+		user.sendMessage("aoneblock.phase.insufficient-bank-balance", TextVariables.NUMBER,
 			String.valueOf(r.getBank()));
 		return true;
 	    }
@@ -145,34 +146,32 @@ private boolean checkBankRequirement(Requirement r, Player player, Island i, Wor
 	}).orElse(false);
     }
 
-    private boolean checkEcoRequirement(Requirement r, Player player, World world) {
+    private boolean checkEcoRequirement(Requirement r, User user, World world) {
 	// Eco checking logic
-	return addon.getPlugin().getVault().map(l -> {
-	    if (l.getBalance(User.getInstance(player), world) < r.getEco()) {
-		User.getInstance(player).sendMessage("aoneblock.phase.insufficient-funds", TextVariables.NUMBER,
-			String.valueOf(r.getEco()));
+	return addon.getPlugin().getVault().map(vaultHook -> {
+	    if (vaultHook.getBalance(user, world) < r.getEco()) {
+		user.sendMessage("aoneblock.phase.insufficient-funds", TextVariables.NUMBER,
+			vaultHook.format(r.getEco()));
 		return true;
 	    }
 	    return false;
 	}).orElse(false);
     }
 
-    private boolean checkPermissionRequirement(Requirement r, Player player) {
+    private boolean checkPermissionRequirement(Requirement r, User user) {
 	// Permission checking logic
-	if (player != null && !player.hasPermission(r.getPermission())) {
-	    User.getInstance(player).sendMessage("aoneblock.phase.insufficient-permission", TextVariables.NAME,
-		    String.valueOf(r.getPermission()));
+	if (user != null && !user.hasPermission(r.getPermission())) {
+	    user.sendMessage("aoneblock.phase.insufficient-permission", TextVariables.NAME, r.getPermission());
 	    return true;
 	}
 	return false;
     }
 
-    private boolean checkCooldownRequirement(Requirement r, Player player, OneBlockIslands is) {
+    private boolean checkCooldownRequirement(Requirement r, User player, OneBlockIslands is) {
 	// Cooldown checking logic
 	long remainingTime = r.getCooldown() - (System.currentTimeMillis() - is.getLastPhaseChangeTime()) / 1000;
 	if (remainingTime > 0) {
-	    User.getInstance(player).sendMessage("aoneblock.phase.cooldown", TextVariables.NUMBER,
-		    String.valueOf(remainingTime));
+	    player.sendMessage("aoneblock.phase.cooldown", TextVariables.NUMBER, String.valueOf(remainingTime));
 	    return true;
 	}
 	return false;

From 7ddd9a414fb85e55769a4aa0a8ccd79b5c67c376 Mon Sep 17 00:00:00 2001
From: "gitlocalize-app[bot]"
 <55277160+gitlocalize-app[bot]@users.noreply.github.com>
Date: Sun, 26 Nov 2023 10:00:16 -0800
Subject: [PATCH 37/42] Add Ukrainian locale (#361)

* Translate uk.yml via GitLocalize

* Translate uk.yml via GitLocalize

* Translate uk.yml via GitLocalize

---------

Co-authored-by: GIGABAIT 
Co-authored-by: mt-gitlocalize 
Co-authored-by: BONNe 
---
 src/main/resources/locales/uk.yml | 91 +++++++++++++++++++++++++++++++
 1 file changed, 91 insertions(+)
 create mode 100644 src/main/resources/locales/uk.yml

diff --git a/src/main/resources/locales/uk.yml b/src/main/resources/locales/uk.yml
new file mode 100644
index 00000000..25a1aa56
--- /dev/null
+++ b/src/main/resources/locales/uk.yml
@@ -0,0 +1,91 @@
+---
+aoneblock:
+  commands:
+    admin:
+      setcount:
+        parameters: "  [lifetime]"
+        description: встановити кількість блоків гравця
+        set: "&a [name] встановлено значення [number]"
+        set-lifetime: "&a [name] тривалість життя встановлено на [number]"
+      setchest:
+        parameters: " "
+        description: поставити скриню, на яку дивляться, у фазу з указаною рідкістю
+        chest-is-empty: "&c Ця скриня порожня, тому її неможливо додати"
+        unknown-phase: "&c Невідома фаза. Використовуйте Tab-complete, щоб побачити
+          їх"
+        unknown-rarity: "&c Невідома рідкість. Використовуйте COMMON, UNCOMMON, RARE
+          або EPIC"
+        look-at-chest: "&c Подивіться на заповнену скриню, щоб встановити її"
+        only-single-chest: "&c Можна встановити лише окремі скрині"
+        success: "& Скриню успішно додано до фази"
+        failure: "&c Скриня не може бути додана до фази! Перегляньте консоль для помилок"
+      sanity:
+        parameters: ""
+        description: відобразити перевірку працездатності ймовірностей фази на консолі
+        see-console: "&a Дивіться консоль для звіту"
+    count:
+      description: показати кількість блоків і фазу
+      info: "&a Ви знаходитесь у блоці &b [number] у фазі &a [name]."
+    info:
+      count: "&a Острів знаходиться на блоці &b [number]&a у фазі &b [name] &a. Підрахунок
+        тривалості життя &b [lifetime] &a."
+    phases:
+      description: показати список усіх фаз
+      title: "&2 OneBlock фази"
+      name-syntax: "&a [name]"
+      description-syntax: "&b [number] блоків"
+    island:
+      setcount:
+        parameters: ""
+        description: встановити кількість блоків до попередньо завершеного значення
+        set: "&a Лічильник встановлено на [number]."
+        too-high: "&c Максимум, який ви можете встановити, це [number]!"
+    respawn-block:
+      description: відроджує магічний блок у ситуаціях, коли він зникає
+      block-exist: "&a Блок існує, не потребує відновлення. Я позначив це для вас."
+      block-respawned: "& Блок відродився."
+  phase:
+    insufficient-level: "&c Рівень вашого острова занадто низький, щоб продовжити!
+      Це має бути [number]."
+    insufficient-funds: "&c Ваших коштів занадто мало, щоб продовжити! Вони мають
+      бути [number]."
+    insufficient-bank-balance: "&c Баланс острівного банку занадто низький, щоб продовжити!
+      Це має бути [number]."
+    insufficient-permission: "&c Ви не можете продовжувати далі, доки не отримаєте
+      дозвіл [name]!"
+    cooldown: "&c Наступна фаза буде доступна через [number] секунд!"
+  placeholders:
+    infinite: Нескінченний
+  gui:
+    titles:
+      phases: "&0&l Фази одного блоку"
+    buttons:
+      previous:
+        name: "&f&l Попередня сторінка"
+        description: "&7 Перейти на сторінку [number]."
+      next:
+        name: "&f&l Наступна сторінка"
+        description: "&7 Перейти на сторінку [number]."
+      phase:
+        name: "&f&l [phase]"
+        description: |-
+          [starting-block]
+          [biome]
+          [bank]
+          [economy]
+          [level]
+          [permission]
+        starting-block: "&7 Запускається після розбиття &e [number] блоків."
+        biome: "&7 Біом: &e [biome]"
+        bank: "&7 Потрібен &e $[number] &7 на банківському рахунку."
+        economy: "&7 Потрібен &e $[number] &7 в обліковому записі гравця."
+        level: "&7 Потрібен рівень острова &e [number] &7."
+        permission: "&7 Потрібен дозвіл `&e[permission]&7`."
+    tips:
+      click-to-previous: "&e Натисніть &7, щоб переглянути попередню сторінку."
+      click-to-next: "&e Натисніть &7, щоб переглянути наступну сторінку."
+      click-to-change: "&e Натисніть &7, щоб змінити."
+  island:
+    starting-hologram: |-
+      &aЛаскаво просимо до AOneBlock
+      &eРозбийте цей блок, щоб почати

From 072fd0b6754e4ce4b2429565a2decacf7b4ce7b8 Mon Sep 17 00:00:00 2001
From: Huynh Tien 
Date: Wed, 29 Nov 2023 09:47:48 +0700
Subject: [PATCH 38/42] Let ItemsAdder be a new OneBlockCustomBlock (#359)

* Let ItemsAdder be a new OneBlockCustomBlock

* fix conflict
---
 .../world/bentobox/aoneblock/AOneBlock.java   |  4 ++
 .../aoneblock/listeners/BlockListener.java    |  8 ----
 .../aoneblock/oneblocks/OneBlockObject.java   | 26 ------------
 .../aoneblock/oneblocks/OneBlockPhase.java    | 11 -----
 .../aoneblock/oneblocks/OneBlocksManager.java | 35 +---------------
 .../customblock/BlockDataCustomBlock.java     |  9 ++--
 .../customblock/ItemsAdderCustomBlock.java    | 41 +++++++++++++++++++
 .../oneblocks/customblock/MobCustomBlock.java |  2 +-
 8 files changed, 51 insertions(+), 85 deletions(-)
 create mode 100644 src/main/java/world/bentobox/aoneblock/oneblocks/customblock/ItemsAdderCustomBlock.java

diff --git a/src/main/java/world/bentobox/aoneblock/AOneBlock.java b/src/main/java/world/bentobox/aoneblock/AOneBlock.java
index 0f646715..2a63c99e 100644
--- a/src/main/java/world/bentobox/aoneblock/AOneBlock.java
+++ b/src/main/java/world/bentobox/aoneblock/AOneBlock.java
@@ -24,7 +24,9 @@
 import world.bentobox.aoneblock.listeners.ItemsAdderListener;
 import world.bentobox.aoneblock.listeners.JoinLeaveListener;
 import world.bentobox.aoneblock.listeners.NoBlockHandler;
+import world.bentobox.aoneblock.oneblocks.OneBlockCustomBlockCreator;
 import world.bentobox.aoneblock.oneblocks.OneBlocksManager;
+import world.bentobox.aoneblock.oneblocks.customblock.ItemsAdderCustomBlock;
 import world.bentobox.aoneblock.requests.IslandStatsHandler;
 import world.bentobox.aoneblock.requests.LocationStatsHandler;
 import world.bentobox.bentobox.api.addons.GameModeAddon;
@@ -57,6 +59,8 @@ public void onLoad() {
 		// Check if ItemsAdder exists, if yes register listener
 		if (Bukkit.getPluginManager().getPlugin("ItemsAdder") != null) {
 			registerListener(new ItemsAdderListener(this));
+			OneBlockCustomBlockCreator.register(ItemsAdderCustomBlock::fromId);
+			OneBlockCustomBlockCreator.register("itemsadder", ItemsAdderCustomBlock::fromMap);
 			hasItemsAdder = true;
 		}
 		// Save the default config from config.yml
diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
index 663c1205..4ba143dc 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
@@ -41,7 +41,6 @@
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 
-import dev.lone.itemsadder.api.CustomBlock;
 import world.bentobox.aoneblock.AOneBlock;
 import world.bentobox.aoneblock.dataobjects.OneBlockIslands;
 import world.bentobox.aoneblock.events.MagicBlockEntityEvent;
@@ -435,13 +434,6 @@ private void breakBlock(@Nullable Player player, Block block, @NonNull OneBlockO
     private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block) {
 	if (nextBlock.isCustomBlock()) {
 	    nextBlock.getCustomBlock().execute(addon, block);
-	} else if (nextBlock.isItemsAdderBlock()) {
-	    // Get Custom Block from ItemsAdder and place it
-	    CustomBlock cBlock = CustomBlock.getInstance(nextBlock.getItemsAdderBlock());
-	    if (cBlock != null) {
-		block.getLocation().getBlock().setType(Material.AIR);
-		cBlock.place(block.getLocation());
-	    }
 	} else {
 	    @NonNull
 	    Material type = nextBlock.getMaterial();
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockObject.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockObject.java
index 64646f8d..b86eb693 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockObject.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockObject.java
@@ -42,7 +42,6 @@ public enum Rarity {
     private Map chest;
     private Rarity rarity;
     private OneBlockCustomBlock customBlock;
-    private String itemsAdderBlock;
     private int prob;
 
     /**
@@ -78,18 +77,6 @@ public OneBlockObject(OneBlockCustomBlock customBlock, int prob) {
         this.prob = prob;
     }
 
-    /**
-     * An ItemsAdder block
-     *
-     * @param namedSpaceID - ItemsAdder block
-     * @param prob        - relative probability
-     */
-
-    public OneBlockObject(String namedSpaceID, int prob) {
-        this.itemsAdderBlock = namedSpaceID;
-        this.prob = prob;
-    }
-
     /**
      * A chest
      *
@@ -113,7 +100,6 @@ public OneBlockObject(OneBlockObject ob) {
         this.rarity = ob.getRarity();
         this.prob = ob.getProb();
         this.customBlock = ob.getCustomBlock();
-        this.itemsAdderBlock = ob.getItemsAdderBlock();
     }
 
     /**
@@ -147,11 +133,6 @@ public OneBlockCustomBlock getCustomBlock() {
         return customBlock;
     }
 
-    /**
-     * @return the itemsAdderBlock
-     */
-    public String getItemsAdderBlock() { return itemsAdderBlock; }
-
 
     /**
      * @return the isMaterial
@@ -176,13 +157,6 @@ public boolean isCustomBlock() {
         return customBlock != null;
     }
 
-    /**
-     * @return the isItemsAdderBlock
-     */
-    public boolean isItemsAdderBlock() {
-        return itemsAdderBlock != null;
-    }
-
     /**
      * @return the rarity
      */
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockPhase.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockPhase.java
index 9f9efbe4..af595537 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockPhase.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlockPhase.java
@@ -165,17 +165,6 @@ public void addCustomBlock(OneBlockCustomBlock customBlock, int prob) {
         probMap.put(total, new OneBlockObject(customBlock, prob));
     }
 
-    /**
-     * Adds a ItemsAdder's custom block and associated probability
-     * @param namedSpaceID - name space and ID
-     * @param prob        - probability
-     */
-    public void addItemsAdderCustomBlock(String namedSpaceID, int prob) {
-        total += prob;
-        blockTotal += prob;
-        probMap.put(total, new OneBlockObject(namedSpaceID, prob));
-    }
-
     /**
      * Adds an entity type and associated probability
      *
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java
index b028f3c2..e5516509 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/OneBlocksManager.java
@@ -31,8 +31,6 @@
 import com.google.common.base.Enums;
 import com.google.common.io.Files;
 
-import dev.lone.itemsadder.api.CustomBlock;
-import dev.lone.itemsadder.api.ItemsAdder;
 import world.bentobox.aoneblock.AOneBlock;
 import world.bentobox.aoneblock.dataobjects.OneBlockIslands;
 import world.bentobox.aoneblock.oneblocks.OneBlockObject.Rarity;
@@ -489,22 +487,7 @@ void addBlocks(OneBlockPhase obPhase, ConfigurationSection phase) {
 	if (phase.isConfigurationSection(BLOCKS)) {
 	    ConfigurationSection blocks = phase.getConfigurationSection(BLOCKS);
 	    for (String material : blocks.getKeys(false)) {
-		if (Material.getMaterial(material) != null) {
-		    addMaterial(obPhase, material, Objects.toString(blocks.get(material)));
-		} else {
-		    if (addon.hasItemsAdder()) {
-			CustomBlock block = CustomBlock.getInstance(material);
-			if (block != null) {
-			    addItemsAdderBlock(obPhase, material, Objects.toString(blocks.get(material)));
-			} else if (ItemsAdder.getAllItems() != null) {
-			    if (ItemsAdder.getAllItems().size() != 0) {
-				addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material);
-			    }
-			}
-		    } else {
-			addon.logError("Bad block material in " + obPhase.getPhaseName() + ": " + material);
-		    }
-		}
+			addMaterial(obPhase, material, Objects.toString(blocks.get(material)));
 	    }
 	} else if (phase.isList(BLOCKS)) {
 	    List> blocks = phase.getMapList(BLOCKS);
@@ -559,22 +542,6 @@ private boolean addMaterial(OneBlockPhase obPhase, String material, String proba
 	return true;
     }
 
-    private void addItemsAdderBlock(OneBlockPhase obPhase, String block, String probability) {
-	int prob;
-	try {
-	    prob = Integer.parseInt(probability);
-	    if (prob < 1) {
-		addon.logWarning("Bad item weight for " + obPhase.getPhaseName() + ": " + block
-			+ ". Must be positive number above 1.");
-	    } else {
-		obPhase.addItemsAdderCustomBlock(block, prob);
-	    }
-	} catch (Exception e) {
-	    addon.logError("Bad item weight for " + obPhase.getPhaseName() + ": " + block + ". Must be a number.");
-	}
-
-    }
-
     /**
      * Return the current phase for the block count
      *
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java
index 1f777022..0acea875 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java
@@ -1,16 +1,15 @@
 package world.bentobox.aoneblock.oneblocks.customblock;
 
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-
 import org.bukkit.Bukkit;
 import org.bukkit.block.Block;
-
 import world.bentobox.aoneblock.AOneBlock;
 import world.bentobox.aoneblock.oneblocks.OneBlockCustomBlock;
 import world.bentobox.bentobox.BentoBox;
 
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
 /**
  * A custom block that is defined by a block data value.
  *
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/ItemsAdderCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/ItemsAdderCustomBlock.java
new file mode 100644
index 00000000..f2ca8485
--- /dev/null
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/ItemsAdderCustomBlock.java
@@ -0,0 +1,41 @@
+package world.bentobox.aoneblock.oneblocks.customblock;
+
+import dev.lone.itemsadder.api.CustomBlock;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import world.bentobox.aoneblock.AOneBlock;
+import world.bentobox.aoneblock.oneblocks.OneBlockCustomBlock;
+import world.bentobox.bentobox.BentoBox;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+public class ItemsAdderCustomBlock implements OneBlockCustomBlock {
+    private final CustomBlock customBlock;
+
+    public ItemsAdderCustomBlock(CustomBlock customBlock) {
+        this.customBlock = customBlock;
+    }
+
+    public static Optional fromId(String id) {
+        return Optional.ofNullable(CustomBlock.getInstance(id)).map(ItemsAdderCustomBlock::new);
+    }
+
+    public static Optional fromMap(Map map) {
+        return Optional
+                .ofNullable(Objects.toString(map.get("id"), null))
+                .flatMap(ItemsAdderCustomBlock::fromId);
+    }
+
+    @Override
+    public void execute(AOneBlock addon, Block block) {
+        try {
+            block.setType(Material.AIR);
+            customBlock.place(block.getLocation());
+        } catch (Exception e) {
+            BentoBox.getInstance().logError("Could not place custom block " + customBlock.getId() + " for block " + block.getType());
+            block.setType(Material.STONE);
+        }
+    }
+}
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java
index 6e95a966..ce03a8cf 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/MobCustomBlock.java
@@ -42,7 +42,7 @@ public static Optional fromMap(Map map) {
         EntityType entityType = maybeEntity(entityTypeValue);
         Material underlyingBlock = Material.getMaterial(underlyingBlockValue);
 
-        if(underlyingBlock == null){
+        if (underlyingBlock == null) {
             BentoBox.getInstance().logWarning("Underlying block " + underlyingBlockValue + " does not exist and will be replaced with STONE.");
         }
 

From d7101b98b71a6b7268a79099e496ff2320a4aecd Mon Sep 17 00:00:00 2001
From: Baterka <3463591+Baterka@users.noreply.github.com>
Date: Wed, 29 Nov 2023 23:43:31 +0100
Subject: [PATCH 39/42] goToBlock fixes (#362)

* - Fixed `goToBlock` that was not working correctly after introducing `cooldown` requirement.

* Removed debug print. Opsie..
---
 .../aoneblock/listeners/BlockListener.java    | 32 +++++++++++--------
 1 file changed, 19 insertions(+), 13 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
index 4ba143dc..90ee62ad 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
@@ -49,6 +49,7 @@
 import world.bentobox.aoneblock.oneblocks.OneBlockObject;
 import world.bentobox.aoneblock.oneblocks.OneBlockPhase;
 import world.bentobox.aoneblock.oneblocks.OneBlocksManager;
+import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.events.island.IslandCreatedEvent;
 import world.bentobox.bentobox.api.events.island.IslandDeleteEvent;
 import world.bentobox.bentobox.api.events.island.IslandResettedEvent;
@@ -274,9 +275,9 @@ private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player
 	// Save previous processing phase name
 	String prevPhaseName = is.getPhaseName();
 
-	// Check for a goto
-	if (Objects.requireNonNull(phase).getGotoBlock() != null) {
-	    handleGoto(is, phase);
+	// Check if phase contains `gotoBlock`
+	if(Objects.requireNonNull(phase).getGotoBlock() != null){
+		phase = handleGoto(is, phase.getGotoBlock());
 	}
 
 	// Get current phase name
@@ -285,9 +286,14 @@ private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player
 	// Get the phase for next block number
 	OneBlockPhase nextPhase = oneBlocksManager.getPhase(is.getBlockNumber() + 1);
 
+	// Check if nextPhase contains `gotoBlock` and override `nextPhase`
+	if (Objects.requireNonNull(nextPhase).getGotoBlock() != null) {
+		nextPhase = oneBlocksManager.getPhase(nextPhase.getGotoBlock());
+	}
+
 	// Get next phase name
 	String nextPhaseName = nextPhase == null || nextPhase.getPhaseName() == null ? "" : nextPhase.getPhaseName();
-
+	
 	// If next phase is new, log break time of the last block of this phase
 	if (!currPhaseName.equalsIgnoreCase(nextPhaseName)) {
 	    is.setLastPhaseChangeTime(System.currentTimeMillis());
@@ -296,10 +302,11 @@ private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player
 	boolean isCurrPhaseNew = !is.getPhaseName().equalsIgnoreCase(currPhaseName);
 
 	if (isCurrPhaseNew) {
+
 	    // Check if requirements for new phase are met
 	    if (check.phaseRequirementsFail(player, i, is, phase, world)) {
-		e.setCancelled(true);
-		return;
+			e.setCancelled(true);
+			return;
 	    }
 
 	    check.setNewPhase(player, i, is, phase);
@@ -379,13 +386,12 @@ private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player
 	is.incrementBlockNumber();
     }
 
-    private void handleGoto(OneBlockIslands is, OneBlockPhase phase) {
-	int gotoBlock = phase.getGotoBlock();
-	phase = oneBlocksManager.getPhase(gotoBlock);
-	// Store lifetime
-	is.setLifetime(is.getLifetime() + gotoBlock);
-	// Set current block
-	is.setBlockNumber(gotoBlock);
+    private OneBlockPhase handleGoto(OneBlockIslands is, int gotoBlock) {
+		// Store lifetime
+		is.setLifetime(is.getLifetime() + gotoBlock);
+		// Set current block
+		is.setBlockNumber(gotoBlock);
+		return oneBlocksManager.getPhase(gotoBlock);
     }
 
     private void setBiome(@NonNull Block block, @Nullable Biome biome) {

From 0db4013517220e8b9f7407914585331a40ba5bcb Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Wed, 29 Nov 2023 16:46:14 -0800
Subject: [PATCH 40/42] Remove unused imports.

---
 .../bentobox/aoneblock/listeners/BlockListener.java   |  1 -
 .../oneblocks/customblock/BlockDataCustomBlock.java   |  9 +++++----
 .../oneblocks/customblock/ItemsAdderCustomBlock.java  | 11 ++++++-----
 3 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
index 90ee62ad..e0135a28 100644
--- a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
+++ b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java
@@ -49,7 +49,6 @@
 import world.bentobox.aoneblock.oneblocks.OneBlockObject;
 import world.bentobox.aoneblock.oneblocks.OneBlockPhase;
 import world.bentobox.aoneblock.oneblocks.OneBlocksManager;
-import world.bentobox.bentobox.BentoBox;
 import world.bentobox.bentobox.api.events.island.IslandCreatedEvent;
 import world.bentobox.bentobox.api.events.island.IslandDeleteEvent;
 import world.bentobox.bentobox.api.events.island.IslandResettedEvent;
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java
index 0acea875..1f777022 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/BlockDataCustomBlock.java
@@ -1,15 +1,16 @@
 package world.bentobox.aoneblock.oneblocks.customblock;
 
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
 import org.bukkit.Bukkit;
 import org.bukkit.block.Block;
+
 import world.bentobox.aoneblock.AOneBlock;
 import world.bentobox.aoneblock.oneblocks.OneBlockCustomBlock;
 import world.bentobox.bentobox.BentoBox;
 
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-
 /**
  * A custom block that is defined by a block data value.
  *
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/ItemsAdderCustomBlock.java b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/ItemsAdderCustomBlock.java
index f2ca8485..34d79235 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/ItemsAdderCustomBlock.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/customblock/ItemsAdderCustomBlock.java
@@ -1,16 +1,17 @@
 package world.bentobox.aoneblock.oneblocks.customblock;
 
-import dev.lone.itemsadder.api.CustomBlock;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
 import org.bukkit.Material;
 import org.bukkit.block.Block;
+
+import dev.lone.itemsadder.api.CustomBlock;
 import world.bentobox.aoneblock.AOneBlock;
 import world.bentobox.aoneblock.oneblocks.OneBlockCustomBlock;
 import world.bentobox.bentobox.BentoBox;
 
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-
 public class ItemsAdderCustomBlock implements OneBlockCustomBlock {
     private final CustomBlock customBlock;
 

From 52fedee2a9240736b72fa1cd7c54e887ce5dbeaa Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Sat, 20 Jan 2024 08:56:09 -0800
Subject: [PATCH 41/42] Fix tests and refactor code

---
 .../commands/island/IslandSetCountCommand.java      |  4 +++-
 .../bentobox/aoneblock/oneblocks/Requirement.java   |  2 --
 .../bentobox/aoneblock/panels/PhasesPanel.java      |  4 ++++
 .../world/bentobox/aoneblock/AOneBlockTest.java     |  5 -----
 .../commands/island/IslandSetCountCommandTest.java  | 13 ++++++++-----
 5 files changed, 15 insertions(+), 13 deletions(-)

diff --git a/src/main/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommand.java b/src/main/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommand.java
index 7d44040b..d455510d 100644
--- a/src/main/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommand.java
+++ b/src/main/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommand.java
@@ -11,6 +11,7 @@
 import world.bentobox.bentobox.api.localization.TextVariables;
 import world.bentobox.bentobox.api.user.User;
 import world.bentobox.bentobox.database.objects.Island;
+import world.bentobox.bentobox.managers.RanksManager;
 import world.bentobox.bentobox.util.Util;
 
 /**
@@ -60,7 +61,8 @@ public boolean execute(User user, String label, List args) {
         Island island = Objects.requireNonNull(getIslands().getIsland(getWorld(), user));
         int rank = island.getRank(user);
         if (rank < island.getRankCommand(getUsage())) {
-            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK, user.getTranslation(getPlugin().getRanksManager().getRank(rank)));
+            user.sendMessage("general.errors.insufficient-rank", TextVariables.RANK,
+                    user.getTranslation(RanksManager.getInstance().getRank(rank)));
             return false;
         }
         // Get value
diff --git a/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java b/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java
index 7a56ce37..cc4f62e0 100644
--- a/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java
+++ b/src/main/java/world/bentobox/aoneblock/oneblocks/Requirement.java
@@ -1,7 +1,5 @@
 package world.bentobox.aoneblock.oneblocks;
 
-import world.bentobox.aoneblock.oneblocks.Requirement.ReqType;
-
 /**
  * Requirement for finishing a phase
  * @author tastybento
diff --git a/src/main/java/world/bentobox/aoneblock/panels/PhasesPanel.java b/src/main/java/world/bentobox/aoneblock/panels/PhasesPanel.java
index 0903b19f..c2643f95 100644
--- a/src/main/java/world/bentobox/aoneblock/panels/PhasesPanel.java
+++ b/src/main/java/world/bentobox/aoneblock/panels/PhasesPanel.java
@@ -402,6 +402,10 @@ private PanelItem createPhaseButton(ItemTemplateRecord template, Map.Entry permissionText.append(this.user.getTranslationOrNothing(reference + "permission",
                     PERMISSION, requirement.getPermission()));
+            case COOLDOWN -> {
+                // do nothing
+            }
+            default -> throw new IllegalArgumentException("Unexpected value: " + requirement.getType());
 
             }
         });
diff --git a/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java b/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java
index 97f43fa7..9d588260 100644
--- a/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java
+++ b/src/test/java/world/bentobox/aoneblock/AOneBlockTest.java
@@ -65,7 +65,6 @@
 import world.bentobox.bentobox.managers.IslandWorldManager;
 import world.bentobox.bentobox.managers.IslandsManager;
 import world.bentobox.bentobox.managers.PlaceholdersManager;
-import world.bentobox.bentobox.managers.RanksManager;
 
 /**
  * @author tastybento
@@ -216,10 +215,6 @@ public void setUp() throws Exception {
 		when(plugin.getFlagsManager()).thenReturn(fm);
 		when(fm.getFlags()).thenReturn(Collections.emptyList());
 
-		// RanksManager
-		RanksManager rm = new RanksManager();
-		when(plugin.getRanksManager()).thenReturn(rm);
-
 	}
 
 	private void add(Path path, JarOutputStream tempJarOutputStream) throws FileNotFoundException, IOException {
diff --git a/src/test/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommandTest.java b/src/test/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommandTest.java
index eabfb3a4..58e50193 100644
--- a/src/test/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommandTest.java
+++ b/src/test/java/world/bentobox/aoneblock/commands/island/IslandSetCountCommandTest.java
@@ -65,7 +65,7 @@
  *
  */
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, DatabaseSetup.class })
+@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, DatabaseSetup.class, RanksManager.class })
 public class IslandSetCountCommandTest {
 	@Mock
 	private BentoBox plugin;
@@ -89,6 +89,9 @@ public class IslandSetCountCommandTest {
 	private IslandSetCountCommand iscc;
 	@Mock
 	private BlockListener bl;
+    @Mock
+    private RanksManager rm;
+
 	private @NonNull OneBlockIslands oneBlockIsland = new OneBlockIslands(UUID.randomUUID().toString());
 
 	private static AbstractDatabaseHandler h;
@@ -131,6 +134,9 @@ public void setUp() throws Exception {
 		BentoBox plugin = mock(BentoBox.class);
 		Whitebox.setInternalState(BentoBox.class, "instance", plugin);
 
+        // Set up RanksManager
+        Whitebox.setInternalState(RanksManager.class, "instance", rm);
+
 		// Command manager
 		CommandsManager cm = mock(CommandsManager.class);
 		when(plugin.getCommandsManager()).thenReturn(cm);
@@ -169,10 +175,6 @@ public void setUp() throws Exception {
 		Settings settings = new Settings();
 		when(addon.getSettings()).thenReturn(settings);
 
-		// RanksManager
-		RanksManager rm = new RanksManager();
-		when(plugin.getRanksManager()).thenReturn(rm);
-
 		// BlockListener
 		when(addon.getBlockListener()).thenReturn(bl);
 		when(bl.getIsland(island)).thenReturn(oneBlockIsland);
@@ -231,6 +233,7 @@ public void testExecuteUserStringListOfStringNoIsland() {
 	 */
 	@Test
 	public void testExecuteUserStringListOfStringLowRank() {
+        when(rm.getRank(anyInt())).thenReturn(RanksManager.MEMBER_RANK_REF);
 		assertFalse(iscc.execute(user, "", List.of("2000")));
 		verify(user).sendMessage("general.errors.insufficient-rank", TextVariables.RANK, RanksManager.MEMBER_RANK_REF);
 	}

From db16529b2e7a4ede226ccc7c1516a55be331986e Mon Sep 17 00:00:00 2001
From: tastybento 
Date: Sat, 20 Jan 2024 08:58:28 -0800
Subject: [PATCH 42/42] Update to 1.20.4. Handle too large Material class.

---
 pom.xml | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index d29310d8..7746d1ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,7 +58,7 @@
         
         2.0.9
         
-        1.20.1-R0.1-SNAPSHOT
+        1.20.4-R0.1-SNAPSHOT
         2.0.0-SNAPSHOT
         2.6.2
         1.3.0
@@ -366,7 +366,9 @@
                         
                         **/*Names*
-                    
+                         
+                        org/bukkit/Material*
+