feat(pocket-base): add purchasable biome changes
This commit is contained in:
@@ -30,6 +30,7 @@ The plugin JAR is written to `build/libs/`.
|
|||||||
/basesettings upgrade
|
/basesettings upgrade
|
||||||
/basesettings pocket upgrade
|
/basesettings pocket upgrade
|
||||||
/basesettings pocket mobs <enable|disable>
|
/basesettings pocket mobs <enable|disable>
|
||||||
|
/basesettings pocket type <void|nether|overworld> <subtype>
|
||||||
/basesettings visitors <allowed|blocked>
|
/basesettings visitors <allowed|blocked>
|
||||||
/basesettings navigation <enable|disable>
|
/basesettings navigation <enable|disable>
|
||||||
/basesettings flight <enable|disable>
|
/basesettings flight <enable|disable>
|
||||||
@@ -46,6 +47,8 @@ After 250 Survival-mode block placements anywhere by default, the spawnable over
|
|||||||
|
|
||||||
Base IV owners can purchase and expand a persistent Pocket Base with `/basesettings pocket upgrade`. Activating a complete diamond-block portal frame inside the normal base with flint and steel opens one public entrance to the owner's grass platform in a private void world. The owner can move the return portal by building and igniting another complete frame inside the unlocked Pocket Base boundary; only the newly activated return portal remains functional. Natural hostile and passive mob spawning is disabled by default and can be controlled by the owner with `/basesettings pocket mobs <enable|disable>`.
|
Base IV owners can purchase and expand a persistent Pocket Base with `/basesettings pocket upgrade`. Activating a complete diamond-block portal frame inside the normal base with flint and steel opens one public entrance to the owner's grass platform in a private void world. The owner can move the return portal by building and igniting another complete frame inside the unlocked Pocket Base boundary; only the newly activated return portal remains functional. Natural hostile and passive mob spawning is disabled by default and can be controlled by the owner with `/basesettings pocket mobs <enable|disable>`.
|
||||||
|
|
||||||
|
Owners can change biome metadata without altering Pocket Base blocks, entities, inventories, or portals by using `/basesettings pocket type <void|nether|overworld> <subtype>`. Void uses `the_void`; Nether and Overworld accept their compatible vanilla biome names, such as `crimson_forest` or `plains`. Each change defaults to 16 netherite blocks, is configurable, and affects applicable natural mob selection when spawning is enabled. The Pocket Base remains a void-generated normal-environment world; dimension-specific mechanics do not change.
|
||||||
|
|
||||||
## Administration
|
## Administration
|
||||||
|
|
||||||
The `spigotbase.admin` permission is granted to server operators by default. `/homeadmin` aliases `/baseadmin`.
|
The `spigotbase.admin` permission is granted to server operators by default. `/homeadmin` aliases `/baseadmin`.
|
||||||
|
|||||||
@@ -105,3 +105,11 @@ description: Chronological record of material decisions affecting the Spigot Bas
|
|||||||
- Persisted one active return portal location while retaining the generated portal as the default for existing Pocket Bases.
|
- Persisted one active return portal location while retaining the generated portal as the default for existing Pocket Bases.
|
||||||
- Made only the selected intact frame functional without removing old frame blocks, preserving the void-return safety fallback.
|
- Made only the selected intact frame functional without removing old frame blocks, preserving the void-return safety fallback.
|
||||||
- Verified the feature with `./gradlew clean check jar`.
|
- Verified the feature with `./gradlew clean check jar`.
|
||||||
|
|
||||||
|
## 2026-08-24 — Purchasable Pocket Base biomes
|
||||||
|
|
||||||
|
- Added `/basesettings pocket type <void|nether|overworld> <subtype>` with type-aware autocomplete and validation.
|
||||||
|
- Defaulted new and unconfigured Pocket Bases to `void/the_void` and persisted each owner's selected biome.
|
||||||
|
- Applied biome metadata across unlocked columns and new expansion rings without changing blocks, entities, inventories, portals, progression, or mob-spawning preferences.
|
||||||
|
- Added an atomic, configurable biome-change payment that defaults to 16 netherite blocks and restores the prior biome and payment on failure.
|
||||||
|
- Verified the feature with `./gradlew clean check jar`.
|
||||||
|
|||||||
@@ -24,3 +24,4 @@ description: Catalog of user stories for the Spigot Base plugin.
|
|||||||
16. [US-016: Build and use Pocket Base portals](us-016-build-and-use-pocket-base-portals.md)
|
16. [US-016: Build and use Pocket Base portals](us-016-build-and-use-pocket-base-portals.md)
|
||||||
17. [US-017: Expand a Pocket Base](us-017-expand-a-pocket-base.md)
|
17. [US-017: Expand a Pocket Base](us-017-expand-a-pocket-base.md)
|
||||||
18. [US-018: Control Pocket Base mob spawning](us-018-control-pocket-base-mob-spawning.md)
|
18. [US-018: Control Pocket Base mob spawning](us-018-control-pocket-base-mob-spawning.md)
|
||||||
|
19. [US-019: Change a Pocket Base biome](us-019-change-a-pocket-base-biome.md)
|
||||||
|
|||||||
@@ -26,3 +26,4 @@ As a **Pocket Base owner**, I want to enable or disable natural mob spawning in
|
|||||||
- [US-015: Unlock a Pocket Base](us-015-unlock-a-pocket-base.md)
|
- [US-015: Unlock a Pocket Base](us-015-unlock-a-pocket-base.md)
|
||||||
- [US-016: Build and use Pocket Base portals](us-016-build-and-use-pocket-base-portals.md)
|
- [US-016: Build and use Pocket Base portals](us-016-build-and-use-pocket-base-portals.md)
|
||||||
- [US-017: Expand a Pocket Base](us-017-expand-a-pocket-base.md)
|
- [US-017: Expand a Pocket Base](us-017-expand-a-pocket-base.md)
|
||||||
|
- [US-019: Change a Pocket Base biome](us-019-change-a-pocket-base-biome.md)
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
type: User Story
|
||||||
|
title: "US-019: Change a Pocket Base biome"
|
||||||
|
description: Let Pocket Base owners purchase biome changes that alter natural mob selection without changing their builds.
|
||||||
|
status: done
|
||||||
|
---
|
||||||
|
|
||||||
|
# US-019: Change a Pocket Base biome
|
||||||
|
|
||||||
|
As a **Pocket Base owner**, I want to change my Pocket Base biome so that I can control its biome-specific atmosphere and natural mob selection without rebuilding it.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [x] A Pocket Base owner can use `/basesettings pocket type <void|nether|overworld> <subtype>` to select a compatible biome.
|
||||||
|
- [x] Void supports `the_void`; Nether supports its vanilla biome subtypes; and Overworld supports its vanilla biome subtypes.
|
||||||
|
- [x] New and previously unconfigured Pocket Bases default to the void type and `the_void` subtype.
|
||||||
|
- [x] Each change atomically charges a configurable currency and amount that default to 16 netherite blocks.
|
||||||
|
- [x] Selecting the active subtype or providing an invalid type/subtype combination is rejected without charging.
|
||||||
|
- [x] Changing subtypes within the same type incurs the configured cost.
|
||||||
|
- [x] The selected biome is applied across every unlocked Pocket Base column without changing blocks, entities, inventories, portals, level, or the mob-spawning preference.
|
||||||
|
- [x] Newly unlocked terrain receives the selected biome.
|
||||||
|
- [x] When natural mob spawning is enabled, Minecraft can use the selected biome for applicable natural mob selection.
|
||||||
|
- [x] The selected type and subtype persist through restarts and appear in `/basesettings status`.
|
||||||
|
- [x] The command, types, and compatible subtypes are offered through contextual autocomplete.
|
||||||
|
- [x] Failed biome application or persistence restores the prior biome and does not consume payment.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [US-015: Unlock a Pocket Base](us-015-unlock-a-pocket-base.md)
|
||||||
|
- [US-017: Expand a Pocket Base](us-017-expand-a-pocket-base.md)
|
||||||
|
- [US-018: Control Pocket Base mob spawning](us-018-control-pocket-base-mob-spawning.md)
|
||||||
@@ -20,6 +20,9 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
|
|||||||
);
|
);
|
||||||
private static final List<String> VISITOR_MODES = List.of("allowed", "blocked");
|
private static final List<String> VISITOR_MODES = List.of("allowed", "blocked");
|
||||||
private static final List<String> ENABLE_MODES = List.of("enable", "disable");
|
private static final List<String> ENABLE_MODES = List.of("enable", "disable");
|
||||||
|
private static final List<String> POCKET_WORLD_TYPES = List.of(
|
||||||
|
"void", "nether", "overworld"
|
||||||
|
);
|
||||||
private final BaseStateManager stateManager;
|
private final BaseStateManager stateManager;
|
||||||
private final PluginSettingsProvider settings;
|
private final PluginSettingsProvider settings;
|
||||||
private final TeleportPolicy teleportPolicy;
|
private final TeleportPolicy teleportPolicy;
|
||||||
@@ -70,6 +73,10 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
|
|||||||
&& arguments[1].equalsIgnoreCase("mobs")) {
|
&& arguments[1].equalsIgnoreCase("mobs")) {
|
||||||
return updatePocketMobSpawning(player, arguments[2]);
|
return updatePocketMobSpawning(player, arguments[2]);
|
||||||
}
|
}
|
||||||
|
if (arguments.length == 4 && arguments[0].equalsIgnoreCase("pocket")
|
||||||
|
&& arguments[1].equalsIgnoreCase("type")) {
|
||||||
|
return purchasePocketBiome(player, arguments[2], arguments[3]);
|
||||||
|
}
|
||||||
if (arguments.length == 2 && arguments[0].equalsIgnoreCase("visitors")) {
|
if (arguments.length == 2 && arguments[0].equalsIgnoreCase("visitors")) {
|
||||||
return updateVisitors(player, state, arguments[1]);
|
return updateVisitors(player, state, arguments[1]);
|
||||||
}
|
}
|
||||||
@@ -180,6 +187,59 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean purchasePocketBiome(
|
||||||
|
Player player,
|
||||||
|
String worldTypeName,
|
||||||
|
String biomeName
|
||||||
|
) {
|
||||||
|
if (pocketBases == null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "Pocket Bases are currently unavailable.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
PocketBaseState current = pocketBases.state(player.getUniqueId());
|
||||||
|
if (current.level() < 1) {
|
||||||
|
player.sendMessage(ChatColor.RED + "Pocket Base I is still locked.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
PocketBaseBiome biome = PocketBaseWorldType.fromCommand(worldTypeName)
|
||||||
|
.flatMap(worldType -> PocketBaseBiome.fromCommand(worldType, biomeName))
|
||||||
|
.orElse(null);
|
||||||
|
if (biome == null) {
|
||||||
|
player.sendMessage(ChatColor.RED + "That Pocket Base type and subtype are invalid.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (biome == current.biome()) {
|
||||||
|
player.sendMessage(ChatColor.RED + "Your Pocket Base already uses "
|
||||||
|
+ biome.commandName() + ".");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int price = settings.current().pocketBaseBiomeChangeCost();
|
||||||
|
Material currency = Material.valueOf(
|
||||||
|
settings.current().pocketBaseBiomeCurrencyMaterial()
|
||||||
|
);
|
||||||
|
PlayerInventory inventory = player.getInventory();
|
||||||
|
if (countCurrency(inventory, currency) < price) {
|
||||||
|
player.sendMessage(ChatColor.RED + "Changing Pocket Base biome costs "
|
||||||
|
+ price + " " + currency.name().toLowerCase(Locale.ROOT) + ".");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ItemStack[] snapshot = cloneContents(inventory.getStorageContents());
|
||||||
|
removeCurrency(inventory, currency, price);
|
||||||
|
final PocketBaseState updated;
|
||||||
|
try {
|
||||||
|
updated = pocketBases.setBiome(player.getUniqueId(), biome);
|
||||||
|
} catch (IOException | RuntimeException exception) {
|
||||||
|
inventory.setStorageContents(snapshot);
|
||||||
|
player.sendMessage(ChatColor.RED
|
||||||
|
+ "The Pocket Base biome change failed; your payment was restored.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
player.sendMessage(ChatColor.GREEN + "Pocket Base biome changed to "
|
||||||
|
+ updated.biome().commandName() + " for " + price + " "
|
||||||
|
+ currency.name().toLowerCase(Locale.ROOT) + ".");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean updatePocketMobSpawning(Player player, String mode) {
|
private boolean updatePocketMobSpawning(Player player, String mode) {
|
||||||
if (pocketBases == null) {
|
if (pocketBases == null) {
|
||||||
player.sendMessage(ChatColor.RED + "Pocket Bases are currently unavailable.");
|
player.sendMessage(ChatColor.RED + "Pocket Bases are currently unavailable.");
|
||||||
@@ -452,7 +512,9 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
|
|||||||
player.sendMessage(colorForPrerequisite(owner.baseLevel() >= 4 && owner.base().isPresent())
|
player.sendMessage(colorForPrerequisite(owner.baseLevel() >= 4 && owner.base().isPresent())
|
||||||
+ "Pocket Base " + pocket.level() + ": " + ChatColor.GRAY + price + " "
|
+ "Pocket Base " + pocket.level() + ": " + ChatColor.GRAY + price + " "
|
||||||
+ settings.current().pocketBaseCurrencyMaterial().toLowerCase(Locale.ROOT)
|
+ settings.current().pocketBaseCurrencyMaterial().toLowerCase(Locale.ROOT)
|
||||||
+ " → /basesettings pocket upgrade; mob spawning="
|
+ " → /basesettings pocket upgrade; type="
|
||||||
|
+ pocket.biome().worldType().commandName() + "/" + pocket.biome().commandName()
|
||||||
|
+ "; mob spawning="
|
||||||
+ (pocket.mobSpawningEnabled() ? "enabled" : "disabled"));
|
+ (pocket.mobSpawningEnabled() ? "enabled" : "disabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,17 +547,33 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
|
|||||||
List<String> modes = arguments[0].equalsIgnoreCase("visitors")
|
List<String> modes = arguments[0].equalsIgnoreCase("visitors")
|
||||||
? VISITOR_MODES
|
? VISITOR_MODES
|
||||||
: switch (arguments[0].toLowerCase(Locale.ROOT)) {
|
: switch (arguments[0].toLowerCase(Locale.ROOT)) {
|
||||||
case "pocket" -> List.of("upgrade", "mobs");
|
case "pocket" -> List.of("upgrade", "mobs", "type");
|
||||||
case "navigation", "flight", "border", "spawnable", "bossbar" -> ENABLE_MODES;
|
case "navigation", "flight", "border", "spawnable", "bossbar" -> ENABLE_MODES;
|
||||||
default -> List.of();
|
default -> List.of();
|
||||||
};
|
};
|
||||||
String prefix = arguments[1].toLowerCase(Locale.ROOT);
|
String prefix = arguments[1].toLowerCase(Locale.ROOT);
|
||||||
return modes.stream().filter(mode -> mode.startsWith(prefix)).toList();
|
return modes.stream().filter(mode -> mode.startsWith(prefix)).toList();
|
||||||
}
|
}
|
||||||
if (arguments.length == 3 && arguments[0].equalsIgnoreCase("pocket")
|
if (arguments.length == 3 && arguments[0].equalsIgnoreCase("pocket")) {
|
||||||
&& arguments[1].equalsIgnoreCase("mobs")) {
|
|
||||||
String prefix = arguments[2].toLowerCase(Locale.ROOT);
|
String prefix = arguments[2].toLowerCase(Locale.ROOT);
|
||||||
return ENABLE_MODES.stream().filter(mode -> mode.startsWith(prefix)).toList();
|
if (arguments[1].equalsIgnoreCase("mobs")) {
|
||||||
|
return ENABLE_MODES.stream().filter(mode -> mode.startsWith(prefix)).toList();
|
||||||
|
}
|
||||||
|
if (arguments[1].equalsIgnoreCase("type")) {
|
||||||
|
return POCKET_WORLD_TYPES.stream()
|
||||||
|
.filter(type -> type.startsWith(prefix))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arguments.length == 4 && arguments[0].equalsIgnoreCase("pocket")
|
||||||
|
&& arguments[1].equalsIgnoreCase("type")) {
|
||||||
|
String prefix = arguments[3].toLowerCase(Locale.ROOT);
|
||||||
|
return PocketBaseWorldType.fromCommand(arguments[2])
|
||||||
|
.map(PocketBaseBiome::commandNames)
|
||||||
|
.orElse(List.of())
|
||||||
|
.stream()
|
||||||
|
.filter(biome -> biome.startsWith(prefix))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
@@ -557,6 +635,7 @@ final class BaseSettingsCommand implements CommandExecutor, TabCompleter {
|
|||||||
private static void sendUsage(Player player) {
|
private static void sendUsage(Player player) {
|
||||||
player.sendMessage(ChatColor.RED + "Usage: /basesettings "
|
player.sendMessage(ChatColor.RED + "Usage: /basesettings "
|
||||||
+ "[status|upgrade|pocket upgrade|pocket mobs <enable|disable>"
|
+ "[status|upgrade|pocket upgrade|pocket mobs <enable|disable>"
|
||||||
|
+ "|pocket type <void|nether|overworld> <subtype>"
|
||||||
+ "|visitors <allowed|blocked>|navigation <enable|disable>"
|
+ "|visitors <allowed|blocked>|navigation <enable|disable>"
|
||||||
+ "|flight <enable|disable>|border <enable|disable>"
|
+ "|flight <enable|disable>|border <enable|disable>"
|
||||||
+ "|spawnable <enable|disable>|bossbar <enable|disable>]");
|
+ "|spawnable <enable|disable>|bossbar <enable|disable>]");
|
||||||
|
|||||||
@@ -39,12 +39,14 @@ public record PluginSettings(
|
|||||||
int visitorUnlockDiamondCost,
|
int visitorUnlockDiamondCost,
|
||||||
int pocketBaseUnlockCost,
|
int pocketBaseUnlockCost,
|
||||||
int pocketBaseUpgradeCost,
|
int pocketBaseUpgradeCost,
|
||||||
|
int pocketBaseBiomeChangeCost,
|
||||||
Set<String> baseUnlockMaterials,
|
Set<String> baseUnlockMaterials,
|
||||||
String stoneExpansionMaterial,
|
String stoneExpansionMaterial,
|
||||||
String deepslateExpansionMaterial,
|
String deepslateExpansionMaterial,
|
||||||
String obsidianExpansionMaterial,
|
String obsidianExpansionMaterial,
|
||||||
String visitorCurrencyMaterial,
|
String visitorCurrencyMaterial,
|
||||||
String pocketBaseCurrencyMaterial,
|
String pocketBaseCurrencyMaterial,
|
||||||
|
String pocketBaseBiomeCurrencyMaterial,
|
||||||
String pocketBasePortalFrameMaterial,
|
String pocketBasePortalFrameMaterial,
|
||||||
Set<String> cooldownExcludedMaterials,
|
Set<String> cooldownExcludedMaterials,
|
||||||
int bossBarDurationTicks,
|
int bossBarDurationTicks,
|
||||||
@@ -85,6 +87,7 @@ public record PluginSettings(
|
|||||||
private static final int DEFAULT_VISITOR_UNLOCK_DIAMOND_COST = 128;
|
private static final int DEFAULT_VISITOR_UNLOCK_DIAMOND_COST = 128;
|
||||||
private static final int DEFAULT_POCKET_BASE_UNLOCK_COST = 64;
|
private static final int DEFAULT_POCKET_BASE_UNLOCK_COST = 64;
|
||||||
private static final int DEFAULT_POCKET_BASE_UPGRADE_COST = 16;
|
private static final int DEFAULT_POCKET_BASE_UPGRADE_COST = 16;
|
||||||
|
private static final int DEFAULT_POCKET_BASE_BIOME_CHANGE_COST = 16;
|
||||||
private static final int DEFAULT_BOSS_BAR_DURATION_TICKS = 60;
|
private static final int DEFAULT_BOSS_BAR_DURATION_TICKS = 60;
|
||||||
private static final int DEFAULT_NAVIGATION_PARTICLE_COUNT = 5;
|
private static final int DEFAULT_NAVIGATION_PARTICLE_COUNT = 5;
|
||||||
private static final int DEFAULT_TITLE_FADE_IN_TICKS = 10;
|
private static final int DEFAULT_TITLE_FADE_IN_TICKS = 10;
|
||||||
@@ -148,6 +151,7 @@ public record PluginSettings(
|
|||||||
requirePositive(visitorUnlockDiamondCost, "visitor-unlock-diamond-cost");
|
requirePositive(visitorUnlockDiamondCost, "visitor-unlock-diamond-cost");
|
||||||
requirePositive(pocketBaseUnlockCost, "pocket-base-unlock-cost");
|
requirePositive(pocketBaseUnlockCost, "pocket-base-unlock-cost");
|
||||||
requirePositive(pocketBaseUpgradeCost, "pocket-base-upgrade-cost");
|
requirePositive(pocketBaseUpgradeCost, "pocket-base-upgrade-cost");
|
||||||
|
requirePositive(pocketBaseBiomeChangeCost, "pocket-base-biome-change-cost");
|
||||||
baseUnlockMaterials = normalizedSet(baseUnlockMaterials, "base-unlock-materials", false);
|
baseUnlockMaterials = normalizedSet(baseUnlockMaterials, "base-unlock-materials", false);
|
||||||
stoneExpansionMaterial = normalizedName(stoneExpansionMaterial, "stone-expansion-material");
|
stoneExpansionMaterial = normalizedName(stoneExpansionMaterial, "stone-expansion-material");
|
||||||
deepslateExpansionMaterial = normalizedName(
|
deepslateExpansionMaterial = normalizedName(
|
||||||
@@ -162,6 +166,9 @@ public record PluginSettings(
|
|||||||
pocketBaseCurrencyMaterial = normalizedName(
|
pocketBaseCurrencyMaterial = normalizedName(
|
||||||
pocketBaseCurrencyMaterial, "pocket-base-currency-material"
|
pocketBaseCurrencyMaterial, "pocket-base-currency-material"
|
||||||
);
|
);
|
||||||
|
pocketBaseBiomeCurrencyMaterial = normalizedName(
|
||||||
|
pocketBaseBiomeCurrencyMaterial, "pocket-base-biome-currency-material"
|
||||||
|
);
|
||||||
pocketBasePortalFrameMaterial = normalizedName(
|
pocketBasePortalFrameMaterial = normalizedName(
|
||||||
pocketBasePortalFrameMaterial, "pocket-base-portal-frame-material"
|
pocketBasePortalFrameMaterial, "pocket-base-portal-frame-material"
|
||||||
);
|
);
|
||||||
@@ -230,12 +237,18 @@ public record PluginSettings(
|
|||||||
integer(values, "visitor-unlock-diamond-cost", DEFAULT_VISITOR_UNLOCK_DIAMOND_COST),
|
integer(values, "visitor-unlock-diamond-cost", DEFAULT_VISITOR_UNLOCK_DIAMOND_COST),
|
||||||
integer(values, "pocket-base-unlock-cost", DEFAULT_POCKET_BASE_UNLOCK_COST),
|
integer(values, "pocket-base-unlock-cost", DEFAULT_POCKET_BASE_UNLOCK_COST),
|
||||||
integer(values, "pocket-base-upgrade-cost", DEFAULT_POCKET_BASE_UPGRADE_COST),
|
integer(values, "pocket-base-upgrade-cost", DEFAULT_POCKET_BASE_UPGRADE_COST),
|
||||||
|
integer(
|
||||||
|
values,
|
||||||
|
"pocket-base-biome-change-cost",
|
||||||
|
DEFAULT_POCKET_BASE_BIOME_CHANGE_COST
|
||||||
|
),
|
||||||
stringSet(values, "base-unlock-materials", Set.of("GRASS_BLOCK", "DIRT")),
|
stringSet(values, "base-unlock-materials", Set.of("GRASS_BLOCK", "DIRT")),
|
||||||
string(values, "stone-expansion-material", "STONE"),
|
string(values, "stone-expansion-material", "STONE"),
|
||||||
string(values, "deepslate-expansion-material", "DEEPSLATE"),
|
string(values, "deepslate-expansion-material", "DEEPSLATE"),
|
||||||
string(values, "obsidian-expansion-material", "OBSIDIAN"),
|
string(values, "obsidian-expansion-material", "OBSIDIAN"),
|
||||||
string(values, "visitor-currency-material", "DIAMOND"),
|
string(values, "visitor-currency-material", "DIAMOND"),
|
||||||
string(values, "pocket-base-currency-material", "DIAMOND_BLOCK"),
|
string(values, "pocket-base-currency-material", "DIAMOND_BLOCK"),
|
||||||
|
string(values, "pocket-base-biome-currency-material", "NETHERITE_BLOCK"),
|
||||||
string(values, "pocket-base-portal-frame-material", "DIAMOND_BLOCK"),
|
string(values, "pocket-base-portal-frame-material", "DIAMOND_BLOCK"),
|
||||||
stringSet(values, "cooldown-excluded-materials", Set.of()),
|
stringSet(values, "cooldown-excluded-materials", Set.of()),
|
||||||
integer(values, "boss-bar-duration-ticks", DEFAULT_BOSS_BAR_DURATION_TICKS),
|
integer(values, "boss-bar-duration-ticks", DEFAULT_BOSS_BAR_DURATION_TICKS),
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ final class PluginSettingsValidator {
|
|||||||
}
|
}
|
||||||
validateCurrency(settings.visitorCurrencyMaterial(), "visitor-currency-material");
|
validateCurrency(settings.visitorCurrencyMaterial(), "visitor-currency-material");
|
||||||
validateCurrency(settings.pocketBaseCurrencyMaterial(), "pocket-base-currency-material");
|
validateCurrency(settings.pocketBaseCurrencyMaterial(), "pocket-base-currency-material");
|
||||||
|
validateCurrency(
|
||||||
|
settings.pocketBaseBiomeCurrencyMaterial(),
|
||||||
|
"pocket-base-biome-currency-material"
|
||||||
|
);
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package games.dmg.spigotbase;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public enum PocketBaseBiome {
|
||||||
|
THE_VOID(PocketBaseWorldType.VOID),
|
||||||
|
|
||||||
|
NETHER_WASTES(PocketBaseWorldType.NETHER),
|
||||||
|
SOUL_SAND_VALLEY(PocketBaseWorldType.NETHER),
|
||||||
|
CRIMSON_FOREST(PocketBaseWorldType.NETHER),
|
||||||
|
WARPED_FOREST(PocketBaseWorldType.NETHER),
|
||||||
|
BASALT_DELTAS(PocketBaseWorldType.NETHER),
|
||||||
|
|
||||||
|
OCEAN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
PLAINS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
DESERT(PocketBaseWorldType.OVERWORLD),
|
||||||
|
WINDSWEPT_HILLS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
FOREST(PocketBaseWorldType.OVERWORLD),
|
||||||
|
TAIGA(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SWAMP(PocketBaseWorldType.OVERWORLD),
|
||||||
|
MANGROVE_SWAMP(PocketBaseWorldType.OVERWORLD),
|
||||||
|
RIVER(PocketBaseWorldType.OVERWORLD),
|
||||||
|
FROZEN_OCEAN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
FROZEN_RIVER(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SNOWY_PLAINS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
MUSHROOM_FIELDS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
BEACH(PocketBaseWorldType.OVERWORLD),
|
||||||
|
JUNGLE(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SPARSE_JUNGLE(PocketBaseWorldType.OVERWORLD),
|
||||||
|
DEEP_OCEAN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
STONY_SHORE(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SNOWY_BEACH(PocketBaseWorldType.OVERWORLD),
|
||||||
|
BIRCH_FOREST(PocketBaseWorldType.OVERWORLD),
|
||||||
|
DARK_FOREST(PocketBaseWorldType.OVERWORLD),
|
||||||
|
PALE_GARDEN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SNOWY_TAIGA(PocketBaseWorldType.OVERWORLD),
|
||||||
|
OLD_GROWTH_PINE_TAIGA(PocketBaseWorldType.OVERWORLD),
|
||||||
|
WINDSWEPT_FOREST(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SAVANNA(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SAVANNA_PLATEAU(PocketBaseWorldType.OVERWORLD),
|
||||||
|
BADLANDS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
WOODED_BADLANDS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
WARM_OCEAN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
LUKEWARM_OCEAN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
COLD_OCEAN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
DEEP_LUKEWARM_OCEAN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
DEEP_COLD_OCEAN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
DEEP_FROZEN_OCEAN(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SUNFLOWER_PLAINS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
WINDSWEPT_GRAVELLY_HILLS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
FLOWER_FOREST(PocketBaseWorldType.OVERWORLD),
|
||||||
|
ICE_SPIKES(PocketBaseWorldType.OVERWORLD),
|
||||||
|
OLD_GROWTH_BIRCH_FOREST(PocketBaseWorldType.OVERWORLD),
|
||||||
|
OLD_GROWTH_SPRUCE_TAIGA(PocketBaseWorldType.OVERWORLD),
|
||||||
|
WINDSWEPT_SAVANNA(PocketBaseWorldType.OVERWORLD),
|
||||||
|
ERODED_BADLANDS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
BAMBOO_JUNGLE(PocketBaseWorldType.OVERWORLD),
|
||||||
|
DRIPSTONE_CAVES(PocketBaseWorldType.OVERWORLD),
|
||||||
|
LUSH_CAVES(PocketBaseWorldType.OVERWORLD),
|
||||||
|
DEEP_DARK(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SULFUR_CAVES(PocketBaseWorldType.OVERWORLD),
|
||||||
|
MEADOW(PocketBaseWorldType.OVERWORLD),
|
||||||
|
GROVE(PocketBaseWorldType.OVERWORLD),
|
||||||
|
SNOWY_SLOPES(PocketBaseWorldType.OVERWORLD),
|
||||||
|
FROZEN_PEAKS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
JAGGED_PEAKS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
STONY_PEAKS(PocketBaseWorldType.OVERWORLD),
|
||||||
|
CHERRY_GROVE(PocketBaseWorldType.OVERWORLD);
|
||||||
|
|
||||||
|
private final PocketBaseWorldType worldType;
|
||||||
|
|
||||||
|
PocketBaseBiome(PocketBaseWorldType worldType) {
|
||||||
|
this.worldType = worldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PocketBaseWorldType worldType() {
|
||||||
|
return worldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String commandName() {
|
||||||
|
return name().toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<PocketBaseBiome> fromCommand(
|
||||||
|
PocketBaseWorldType worldType,
|
||||||
|
String value
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
PocketBaseBiome biome = valueOf(value.toUpperCase(Locale.ROOT));
|
||||||
|
return biome.worldType == worldType ? Optional.of(biome) : Optional.empty();
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> commandNames(PocketBaseWorldType worldType) {
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(biome -> biome.worldType == worldType)
|
||||||
|
.map(PocketBaseBiome::commandName)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ final class PocketBaseManager {
|
|||||||
PocketBaseState current = states.state(ownerId);
|
PocketBaseState current = states.state(ownerId);
|
||||||
int nextLevel = Math.addExact(current.level(), 1);
|
int nextLevel = Math.addExact(current.level(), 1);
|
||||||
policy.size(nextLevel);
|
policy.size(nextLevel);
|
||||||
worlds.expand(ownerId, current.level(), nextLevel);
|
worlds.expand(ownerId, current.level(), nextLevel, current.biome());
|
||||||
return states.updateAndSave(ownerId, state -> state.withLevel(nextLevel));
|
return states.updateAndSave(ownerId, state -> state.withLevel(nextLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +71,27 @@ final class PocketBaseManager {
|
|||||||
return states.updateAndSave(ownerId, state -> state.withReturnPortal(portal));
|
return states.updateAndSave(ownerId, state -> state.withReturnPortal(portal));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PocketBaseState setBiome(UUID ownerId, PocketBaseBiome biome) throws IOException {
|
||||||
|
PocketBaseState current = states.state(ownerId);
|
||||||
|
if (current.level() < 1) {
|
||||||
|
throw new IllegalStateException("Pocket Base I is still locked");
|
||||||
|
}
|
||||||
|
if (current.biome() == biome) {
|
||||||
|
throw new IllegalArgumentException("Pocket Base already uses that biome");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
worlds.setBiome(ownerId, current.level(), biome);
|
||||||
|
return states.updateAndSave(ownerId, state -> state.withBiome(biome));
|
||||||
|
} catch (IOException | RuntimeException exception) {
|
||||||
|
try {
|
||||||
|
worlds.setBiome(ownerId, current.level(), current.biome());
|
||||||
|
} catch (RuntimeException rollbackFailure) {
|
||||||
|
exception.addSuppressed(rollbackFailure);
|
||||||
|
}
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PocketBaseState setMobSpawning(UUID ownerId, boolean enabled) throws IOException {
|
PocketBaseState setMobSpawning(UUID ownerId, boolean enabled) throws IOException {
|
||||||
PocketBaseState current = states.state(ownerId);
|
PocketBaseState current = states.state(ownerId);
|
||||||
if (current.level() < 1) {
|
if (current.level() < 1) {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ public record PocketBaseState(
|
|||||||
int level,
|
int level,
|
||||||
Optional<PocketPortalLocation> entrance,
|
Optional<PocketPortalLocation> entrance,
|
||||||
Optional<PocketPortalLocation> returnPortal,
|
Optional<PocketPortalLocation> returnPortal,
|
||||||
boolean mobSpawningEnabled
|
boolean mobSpawningEnabled,
|
||||||
|
PocketBaseBiome biome
|
||||||
) {
|
) {
|
||||||
public PocketBaseState {
|
public PocketBaseState {
|
||||||
if (ownerId == null) {
|
if (ownerId == null) {
|
||||||
@@ -19,6 +20,7 @@ public record PocketBaseState(
|
|||||||
}
|
}
|
||||||
entrance = entrance == null ? Optional.empty() : entrance;
|
entrance = entrance == null ? Optional.empty() : entrance;
|
||||||
returnPortal = returnPortal == null ? Optional.empty() : returnPortal;
|
returnPortal = returnPortal == null ? Optional.empty() : returnPortal;
|
||||||
|
biome = biome == null ? PocketBaseBiome.THE_VOID : biome;
|
||||||
if (level == 0 && (entrance.isPresent() || returnPortal.isPresent())) {
|
if (level == 0 && (entrance.isPresent() || returnPortal.isPresent())) {
|
||||||
throw new IllegalArgumentException("a locked Pocket Base cannot have a portal");
|
throw new IllegalArgumentException("a locked Pocket Base cannot have a portal");
|
||||||
}
|
}
|
||||||
@@ -29,7 +31,9 @@ public record PocketBaseState(
|
|||||||
int level,
|
int level,
|
||||||
Optional<PocketPortalLocation> entrance
|
Optional<PocketPortalLocation> entrance
|
||||||
) {
|
) {
|
||||||
this(ownerId, level, entrance, Optional.empty(), false);
|
this(
|
||||||
|
ownerId, level, entrance, Optional.empty(), false, PocketBaseBiome.THE_VOID
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PocketBaseState(
|
public PocketBaseState(
|
||||||
@@ -38,40 +42,63 @@ public record PocketBaseState(
|
|||||||
Optional<PocketPortalLocation> entrance,
|
Optional<PocketPortalLocation> entrance,
|
||||||
boolean mobSpawningEnabled
|
boolean mobSpawningEnabled
|
||||||
) {
|
) {
|
||||||
this(ownerId, level, entrance, Optional.empty(), mobSpawningEnabled);
|
this(
|
||||||
|
ownerId, level, entrance, Optional.empty(), mobSpawningEnabled,
|
||||||
|
PocketBaseBiome.THE_VOID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PocketBaseState(
|
||||||
|
UUID ownerId,
|
||||||
|
int level,
|
||||||
|
Optional<PocketPortalLocation> entrance,
|
||||||
|
Optional<PocketPortalLocation> returnPortal,
|
||||||
|
boolean mobSpawningEnabled
|
||||||
|
) {
|
||||||
|
this(
|
||||||
|
ownerId, level, entrance, returnPortal, mobSpawningEnabled,
|
||||||
|
PocketBaseBiome.THE_VOID
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PocketBaseState locked(UUID ownerId) {
|
public static PocketBaseState locked(UUID ownerId) {
|
||||||
return new PocketBaseState(
|
return new PocketBaseState(
|
||||||
ownerId, 0, Optional.empty(), Optional.empty(), false
|
ownerId, 0, Optional.empty(), Optional.empty(), false,
|
||||||
|
PocketBaseBiome.THE_VOID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PocketBaseState withLevel(int newLevel) {
|
public PocketBaseState withLevel(int newLevel) {
|
||||||
return new PocketBaseState(
|
return new PocketBaseState(
|
||||||
ownerId, newLevel, entrance, returnPortal, mobSpawningEnabled
|
ownerId, newLevel, entrance, returnPortal, mobSpawningEnabled, biome
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PocketBaseState withEntrance(PocketPortalLocation portal) {
|
public PocketBaseState withEntrance(PocketPortalLocation portal) {
|
||||||
return new PocketBaseState(
|
return new PocketBaseState(
|
||||||
ownerId, level, Optional.of(portal), returnPortal, mobSpawningEnabled
|
ownerId, level, Optional.of(portal), returnPortal, mobSpawningEnabled, biome
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PocketBaseState withoutEntrance() {
|
public PocketBaseState withoutEntrance() {
|
||||||
return new PocketBaseState(
|
return new PocketBaseState(
|
||||||
ownerId, level, Optional.empty(), returnPortal, mobSpawningEnabled
|
ownerId, level, Optional.empty(), returnPortal, mobSpawningEnabled, biome
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PocketBaseState withReturnPortal(PocketPortalLocation portal) {
|
public PocketBaseState withReturnPortal(PocketPortalLocation portal) {
|
||||||
return new PocketBaseState(
|
return new PocketBaseState(
|
||||||
ownerId, level, entrance, Optional.of(portal), mobSpawningEnabled
|
ownerId, level, entrance, Optional.of(portal), mobSpawningEnabled, biome
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PocketBaseState withMobSpawningEnabled(boolean enabled) {
|
public PocketBaseState withMobSpawningEnabled(boolean enabled) {
|
||||||
return new PocketBaseState(ownerId, level, entrance, returnPortal, enabled);
|
return new PocketBaseState(ownerId, level, entrance, returnPortal, enabled, biome);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PocketBaseState withBiome(PocketBaseBiome selectedBiome) {
|
||||||
|
return new PocketBaseState(
|
||||||
|
ownerId, level, entrance, returnPortal, mobSpawningEnabled, selectedBiome
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.Registry;
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.WorldCreator;
|
import org.bukkit.WorldCreator;
|
||||||
|
import org.bukkit.block.Biome;
|
||||||
|
|
||||||
final class PocketBaseWorldService {
|
final class PocketBaseWorldService {
|
||||||
private static final int PLATFORM_Y = 64;
|
private static final int PLATFORM_Y = 64;
|
||||||
@@ -16,23 +20,43 @@ final class PocketBaseWorldService {
|
|||||||
private final Server server;
|
private final Server server;
|
||||||
private final PluginSettingsProvider settings;
|
private final PluginSettingsProvider settings;
|
||||||
private final PocketBasePolicy policy;
|
private final PocketBasePolicy policy;
|
||||||
|
private final Function<PocketBaseBiome, Biome> biomeResolver;
|
||||||
private final Map<UUID, UUID> ownersByWorld = new HashMap<>();
|
private final Map<UUID, UUID> ownersByWorld = new HashMap<>();
|
||||||
|
|
||||||
PocketBaseWorldService(Server server, PluginSettingsProvider settings) {
|
PocketBaseWorldService(Server server, PluginSettingsProvider settings) {
|
||||||
|
this(server, settings, biome -> resolveBiome(server, biome));
|
||||||
|
}
|
||||||
|
|
||||||
|
PocketBaseWorldService(
|
||||||
|
Server server,
|
||||||
|
PluginSettingsProvider settings,
|
||||||
|
Function<PocketBaseBiome, Biome> biomeResolver
|
||||||
|
) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.policy = new PocketBasePolicy(settings);
|
this.policy = new PocketBasePolicy(settings);
|
||||||
|
this.biomeResolver = biomeResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadExisting(Map<UUID, PocketBaseState> states) {
|
void loadExisting(Map<UUID, PocketBaseState> states) {
|
||||||
for (PocketBaseState state : states.values()) {
|
for (PocketBaseState state : states.values()) {
|
||||||
if (state.level() > 0) {
|
if (state.level() > 0) {
|
||||||
|
setBiome(state.ownerId(), state.level(), state.biome());
|
||||||
setMobSpawning(state.ownerId(), state.mobSpawningEnabled());
|
setMobSpawning(state.ownerId(), state.mobSpawningEnabled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
World expand(UUID ownerId, int previousLevel, int newLevel) {
|
World expand(UUID ownerId, int previousLevel, int newLevel) {
|
||||||
|
return expand(ownerId, previousLevel, newLevel, PocketBaseBiome.THE_VOID);
|
||||||
|
}
|
||||||
|
|
||||||
|
World expand(
|
||||||
|
UUID ownerId,
|
||||||
|
int previousLevel,
|
||||||
|
int newLevel,
|
||||||
|
PocketBaseBiome biome
|
||||||
|
) {
|
||||||
World world = ensureWorld(ownerId);
|
World world = ensureWorld(ownerId);
|
||||||
int newHalfSize = policy.size(newLevel) / 2;
|
int newHalfSize = policy.size(newLevel) / 2;
|
||||||
int previousHalfSize = previousLevel == 0 ? 0 : policy.size(previousLevel) / 2;
|
int previousHalfSize = previousLevel == 0 ? 0 : policy.size(previousLevel) / 2;
|
||||||
@@ -50,6 +74,7 @@ final class PocketBaseWorldService {
|
|||||||
world.getBlockAt(x, PLATFORM_Y, z).setType(Material.GRASS_BLOCK, false);
|
world.getBlockAt(x, PLATFORM_Y, z).setType(Material.GRASS_BLOCK, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
applyBiome(world, previousLevel, newLevel, biome);
|
||||||
if (previousLevel == 0) {
|
if (previousLevel == 0) {
|
||||||
createReturnPortal(world);
|
createReturnPortal(world);
|
||||||
world.setSpawnLocation(0, PLATFORM_Y + 1, 5);
|
world.setSpawnLocation(0, PLATFORM_Y + 1, 5);
|
||||||
@@ -70,6 +95,12 @@ final class PocketBaseWorldService {
|
|||||||
ensureWorld(ownerId).setSpawnFlags(enabled, enabled);
|
ensureWorld(ownerId).setSpawnFlags(enabled, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setBiome(UUID ownerId, int level, PocketBaseBiome biome) {
|
||||||
|
World world = ensureWorld(ownerId);
|
||||||
|
applyBiome(world, 0, level, biome);
|
||||||
|
world.save();
|
||||||
|
}
|
||||||
|
|
||||||
Location arrival(UUID ownerId) {
|
Location arrival(UUID ownerId) {
|
||||||
return new Location(ensureWorld(ownerId), 0.5, PLATFORM_Y + 1, 5.5, 180.0f, 0.0f);
|
return new Location(ensureWorld(ownerId), 0.5, PLATFORM_Y + 1, 5.5, 180.0f, 0.0f);
|
||||||
}
|
}
|
||||||
@@ -114,6 +145,40 @@ final class PocketBaseWorldService {
|
|||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyBiome(
|
||||||
|
World world,
|
||||||
|
int previousLevel,
|
||||||
|
int newLevel,
|
||||||
|
PocketBaseBiome selectedBiome
|
||||||
|
) {
|
||||||
|
int newHalfSize = policy.size(newLevel) / 2;
|
||||||
|
int previousHalfSize = previousLevel == 0 ? 0 : policy.size(previousLevel) / 2;
|
||||||
|
Biome biome = biomeResolver.apply(selectedBiome);
|
||||||
|
for (int x = -newHalfSize; x < newHalfSize; x += 4) {
|
||||||
|
for (int z = -newHalfSize; z < newHalfSize; z += 4) {
|
||||||
|
boolean previouslyUnlocked = previousLevel > 0
|
||||||
|
&& x >= -previousHalfSize && x < previousHalfSize
|
||||||
|
&& z >= -previousHalfSize && z < previousHalfSize;
|
||||||
|
if (previouslyUnlocked) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int y = world.getMinHeight(); y < world.getMaxHeight(); y += 4) {
|
||||||
|
world.setBiome(x, y, z, biome);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Biome resolveBiome(Server server, PocketBaseBiome selectedBiome) {
|
||||||
|
Registry<Biome> biomeRegistry = server.getRegistry(Biome.class);
|
||||||
|
if (biomeRegistry == null) {
|
||||||
|
throw new IllegalStateException("Biome registry is unavailable");
|
||||||
|
}
|
||||||
|
return biomeRegistry.getOrThrow(
|
||||||
|
NamespacedKey.minecraft(selectedBiome.commandName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private void createReturnPortal(World world) {
|
private void createReturnPortal(World world) {
|
||||||
PocketPortalLocation portal = new PocketPortalLocation(
|
PocketPortalLocation portal = new PocketPortalLocation(
|
||||||
world.getUID(), world.getName(), -2, PLATFORM_Y + 1, 0, PocketPortalAxis.X
|
world.getUID(), world.getName(), -2, PLATFORM_Y + 1, 0, PocketPortalAxis.X
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package games.dmg.spigotbase;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public enum PocketBaseWorldType {
|
||||||
|
VOID,
|
||||||
|
NETHER,
|
||||||
|
OVERWORLD;
|
||||||
|
|
||||||
|
public String commandName() {
|
||||||
|
return name().toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<PocketBaseWorldType> fromCommand(String value) {
|
||||||
|
try {
|
||||||
|
return Optional.of(valueOf(value.toUpperCase(Locale.ROOT)));
|
||||||
|
} catch (IllegalArgumentException exception) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,7 +44,8 @@ public final class YamlPocketBaseRepository {
|
|||||||
level,
|
level,
|
||||||
loadPortal(yaml, path + ".entrance"),
|
loadPortal(yaml, path + ".entrance"),
|
||||||
loadPortal(yaml, path + ".return-portal"),
|
loadPortal(yaml, path + ".return-portal"),
|
||||||
yaml.getBoolean(path + ".mob-spawning-enabled", false)
|
yaml.getBoolean(path + ".mob-spawning-enabled", false),
|
||||||
|
loadBiome(yaml, path)
|
||||||
);
|
);
|
||||||
states.put(ownerId, state);
|
states.put(ownerId, state);
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
@@ -64,6 +65,8 @@ public final class YamlPocketBaseRepository {
|
|||||||
String path = "owners." + state.ownerId();
|
String path = "owners." + state.ownerId();
|
||||||
yaml.set(path + ".level", state.level());
|
yaml.set(path + ".level", state.level());
|
||||||
yaml.set(path + ".mob-spawning-enabled", state.mobSpawningEnabled());
|
yaml.set(path + ".mob-spawning-enabled", state.mobSpawningEnabled());
|
||||||
|
yaml.set(path + ".world-type", state.biome().worldType().commandName());
|
||||||
|
yaml.set(path + ".biome", state.biome().commandName());
|
||||||
state.entrance().ifPresent(portal -> savePortal(yaml, path + ".entrance", portal));
|
state.entrance().ifPresent(portal -> savePortal(yaml, path + ".entrance", portal));
|
||||||
state.returnPortal().ifPresent(portal ->
|
state.returnPortal().ifPresent(portal ->
|
||||||
savePortal(yaml, path + ".return-portal", portal)
|
savePortal(yaml, path + ".return-portal", portal)
|
||||||
@@ -87,6 +90,17 @@ public final class YamlPocketBaseRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PocketBaseBiome loadBiome(YamlConfiguration yaml, String path) {
|
||||||
|
String worldTypeName = yaml.getString(path + ".world-type");
|
||||||
|
String biomeName = yaml.getString(path + ".biome");
|
||||||
|
if (worldTypeName == null || biomeName == null) {
|
||||||
|
return PocketBaseBiome.THE_VOID;
|
||||||
|
}
|
||||||
|
return PocketBaseWorldType.fromCommand(worldTypeName)
|
||||||
|
.flatMap(worldType -> PocketBaseBiome.fromCommand(worldType, biomeName))
|
||||||
|
.orElse(PocketBaseBiome.THE_VOID);
|
||||||
|
}
|
||||||
|
|
||||||
private static Optional<PocketPortalLocation> loadPortal(
|
private static Optional<PocketPortalLocation> loadPortal(
|
||||||
YamlConfiguration yaml,
|
YamlConfiguration yaml,
|
||||||
String path
|
String path
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ visitor-currency-material: DIAMOND
|
|||||||
pocket-base-unlock-cost: 64
|
pocket-base-unlock-cost: 64
|
||||||
pocket-base-upgrade-cost: 16
|
pocket-base-upgrade-cost: 16
|
||||||
pocket-base-currency-material: DIAMOND_BLOCK
|
pocket-base-currency-material: DIAMOND_BLOCK
|
||||||
|
pocket-base-biome-change-cost: 16
|
||||||
|
pocket-base-biome-currency-material: NETHERITE_BLOCK
|
||||||
pocket-base-portal-frame-material: DIAMOND_BLOCK
|
pocket-base-portal-frame-material: DIAMOND_BLOCK
|
||||||
|
|
||||||
# Spawnable block overlay
|
# Spawnable block overlay
|
||||||
|
|||||||
@@ -100,6 +100,25 @@ final class BaseSettingsCommandTest {
|
|||||||
List.of("mobs"),
|
List.of("mobs"),
|
||||||
command.onTabComplete(null, null, "basesettings", new String[] {"pocket", "m"})
|
command.onTabComplete(null, null, "basesettings", new String[] {"pocket", "m"})
|
||||||
);
|
);
|
||||||
|
assertEquals(
|
||||||
|
List.of("type"),
|
||||||
|
command.onTabComplete(null, null, "basesettings", new String[] {"pocket", "t"})
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
List.of("nether"),
|
||||||
|
command.onTabComplete(
|
||||||
|
null, null, "basesettings", new String[] {"pocket", "type", "n"}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
List.of("crimson_forest"),
|
||||||
|
command.onTabComplete(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"basesettings",
|
||||||
|
new String[] {"pocket", "type", "nether", "c"}
|
||||||
|
)
|
||||||
|
);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
List.of("enable"),
|
List.of("enable"),
|
||||||
command.onTabComplete(
|
command.onTabComplete(
|
||||||
@@ -209,6 +228,73 @@ final class BaseSettingsCommandTest {
|
|||||||
verify(inventory).setStorageContents(any(ItemStack[].class));
|
verify(inventory).setStorageContents(any(ItemStack[].class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void pocketOwnerCanPurchaseBiomeChangeWithConfiguredCurrency() throws Exception {
|
||||||
|
UUID playerId = UUID.randomUUID();
|
||||||
|
Player player = mock(Player.class);
|
||||||
|
when(player.getUniqueId()).thenReturn(playerId);
|
||||||
|
when(player.getName()).thenReturn("Builder");
|
||||||
|
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||||
|
when(player.getInventory()).thenReturn(inventory);
|
||||||
|
when(inventory.getStorageContents()).thenReturn(new ItemStack[] {
|
||||||
|
new ItemStack(Material.NETHERITE_BLOCK, 16)
|
||||||
|
});
|
||||||
|
BaseStateManager stateManager = mock(BaseStateManager.class);
|
||||||
|
when(stateManager.player(playerId, "Builder"))
|
||||||
|
.thenReturn(PlayerState.newPlayer(playerId, "Builder"));
|
||||||
|
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||||
|
PocketBaseState current = new PocketBaseState(playerId, 1, java.util.Optional.empty());
|
||||||
|
when(pocketBases.state(playerId)).thenReturn(current);
|
||||||
|
when(pocketBases.setBiome(playerId, PocketBaseBiome.CRIMSON_FOREST))
|
||||||
|
.thenReturn(current.withBiome(PocketBaseBiome.CRIMSON_FOREST));
|
||||||
|
BaseSettingsCommand command = new BaseSettingsCommand(
|
||||||
|
stateManager,
|
||||||
|
new PluginSettingsProvider(PluginSettings.from(Map.of())),
|
||||||
|
mock(BaseFlightController.class),
|
||||||
|
pocketBases
|
||||||
|
);
|
||||||
|
|
||||||
|
command.onCommand(player, null, "basesettings", new String[] {
|
||||||
|
"pocket", "type", "nether", "crimson_forest"
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(pocketBases).setBiome(playerId, PocketBaseBiome.CRIMSON_FOREST);
|
||||||
|
verify(inventory).setStorageContents(any(ItemStack[].class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selectingCurrentPocketBiomeDoesNotCharge() throws Exception {
|
||||||
|
UUID playerId = UUID.randomUUID();
|
||||||
|
Player player = mock(Player.class);
|
||||||
|
when(player.getUniqueId()).thenReturn(playerId);
|
||||||
|
when(player.getName()).thenReturn("Builder");
|
||||||
|
PlayerInventory inventory = mock(PlayerInventory.class);
|
||||||
|
when(player.getInventory()).thenReturn(inventory);
|
||||||
|
BaseStateManager stateManager = mock(BaseStateManager.class);
|
||||||
|
when(stateManager.player(playerId, "Builder"))
|
||||||
|
.thenReturn(PlayerState.newPlayer(playerId, "Builder"));
|
||||||
|
PocketBaseManager pocketBases = mock(PocketBaseManager.class);
|
||||||
|
when(pocketBases.state(playerId)).thenReturn(
|
||||||
|
new PocketBaseState(playerId, 1, java.util.Optional.empty())
|
||||||
|
);
|
||||||
|
BaseSettingsCommand command = new BaseSettingsCommand(
|
||||||
|
stateManager,
|
||||||
|
new PluginSettingsProvider(PluginSettings.from(Map.of())),
|
||||||
|
mock(BaseFlightController.class),
|
||||||
|
pocketBases
|
||||||
|
);
|
||||||
|
|
||||||
|
command.onCommand(player, null, "basesettings", new String[] {
|
||||||
|
"pocket", "type", "void", "the_void"
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(pocketBases, never()).setBiome(
|
||||||
|
org.mockito.ArgumentMatchers.eq(playerId),
|
||||||
|
org.mockito.ArgumentMatchers.any(PocketBaseBiome.class)
|
||||||
|
);
|
||||||
|
verify(inventory, never()).setStorageContents(any(ItemStack[].class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void pocketOwnerCanEnableNaturalMobSpawning() throws Exception {
|
void pocketOwnerCanEnableNaturalMobSpawning() throws Exception {
|
||||||
UUID playerId = UUID.randomUUID();
|
UUID playerId = UUID.randomUUID();
|
||||||
@@ -366,6 +452,7 @@ final class BaseSettingsCommandTest {
|
|||||||
|
|
||||||
verify(player).sendMessage(org.mockito.ArgumentMatchers.<String>argThat(
|
verify(player).sendMessage(org.mockito.ArgumentMatchers.<String>argThat(
|
||||||
message -> message.contains("Pocket Base 1")
|
message -> message.contains("Pocket Base 1")
|
||||||
|
&& message.contains("type=void/the_void")
|
||||||
&& message.contains("mob spawning=disabled")
|
&& message.contains("mob spawning=disabled")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ final class PluginSettingsTest {
|
|||||||
assertEquals(16, settings.pocketBaseUpgradeCost());
|
assertEquals(16, settings.pocketBaseUpgradeCost());
|
||||||
assertEquals("DIAMOND_BLOCK", settings.pocketBaseCurrencyMaterial());
|
assertEquals("DIAMOND_BLOCK", settings.pocketBaseCurrencyMaterial());
|
||||||
assertEquals("DIAMOND_BLOCK", settings.pocketBasePortalFrameMaterial());
|
assertEquals("DIAMOND_BLOCK", settings.pocketBasePortalFrameMaterial());
|
||||||
|
assertEquals(16, settings.pocketBaseBiomeChangeCost());
|
||||||
|
assertEquals("NETHERITE_BLOCK", settings.pocketBaseBiomeCurrencyMaterial());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -33,13 +35,17 @@ final class PluginSettingsTest {
|
|||||||
"pocket-base-unlock-cost", 32,
|
"pocket-base-unlock-cost", 32,
|
||||||
"pocket-base-upgrade-cost", 8,
|
"pocket-base-upgrade-cost", 8,
|
||||||
"pocket-base-currency-material", "EMERALD_BLOCK",
|
"pocket-base-currency-material", "EMERALD_BLOCK",
|
||||||
"pocket-base-portal-frame-material", "GOLD_BLOCK"
|
"pocket-base-portal-frame-material", "GOLD_BLOCK",
|
||||||
|
"pocket-base-biome-change-cost", 12,
|
||||||
|
"pocket-base-biome-currency-material", "NETHERITE_INGOT"
|
||||||
));
|
));
|
||||||
|
|
||||||
assertEquals(32, settings.pocketBaseUnlockCost());
|
assertEquals(32, settings.pocketBaseUnlockCost());
|
||||||
assertEquals(8, settings.pocketBaseUpgradeCost());
|
assertEquals(8, settings.pocketBaseUpgradeCost());
|
||||||
assertEquals("EMERALD_BLOCK", settings.pocketBaseCurrencyMaterial());
|
assertEquals("EMERALD_BLOCK", settings.pocketBaseCurrencyMaterial());
|
||||||
assertEquals("GOLD_BLOCK", settings.pocketBasePortalFrameMaterial());
|
assertEquals("GOLD_BLOCK", settings.pocketBasePortalFrameMaterial());
|
||||||
|
assertEquals(12, settings.pocketBaseBiomeChangeCost());
|
||||||
|
assertEquals("NETHERITE_INGOT", settings.pocketBaseBiomeCurrencyMaterial());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -85,6 +85,57 @@ final class PocketBaseManagerTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void persistsBiomeAfterApplyingItToTheUnlockedArea() throws Exception {
|
||||||
|
UUID ownerId = UUID.randomUUID();
|
||||||
|
PocketBaseState current = new PocketBaseState(ownerId, 2, Optional.empty());
|
||||||
|
PocketBaseStateManager states = mock(PocketBaseStateManager.class);
|
||||||
|
PocketBaseWorldService worlds = mock(PocketBaseWorldService.class);
|
||||||
|
when(states.state(ownerId)).thenReturn(current);
|
||||||
|
when(states.updateAndSave(org.mockito.ArgumentMatchers.eq(ownerId),
|
||||||
|
org.mockito.ArgumentMatchers.any())).thenAnswer(invocation -> {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
UnaryOperator<PocketBaseState> operation = invocation.getArgument(1);
|
||||||
|
return operation.apply(current);
|
||||||
|
});
|
||||||
|
PocketBaseManager manager = new PocketBaseManager(
|
||||||
|
states,
|
||||||
|
worlds,
|
||||||
|
new PluginSettingsProvider(PluginSettings.from(Map.of()))
|
||||||
|
);
|
||||||
|
|
||||||
|
PocketBaseState updated = manager.setBiome(ownerId, PocketBaseBiome.WARPED_FOREST);
|
||||||
|
|
||||||
|
assertEquals(PocketBaseBiome.WARPED_FOREST, updated.biome());
|
||||||
|
InOrder order = inOrder(worlds, states);
|
||||||
|
order.verify(worlds).setBiome(ownerId, 2, PocketBaseBiome.WARPED_FOREST);
|
||||||
|
order.verify(states).updateAndSave(
|
||||||
|
org.mockito.ArgumentMatchers.eq(ownerId), org.mockito.ArgumentMatchers.any()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void restoresPreviousBiomeWhenPersistenceFails() throws Exception {
|
||||||
|
UUID ownerId = UUID.randomUUID();
|
||||||
|
PocketBaseState current = new PocketBaseState(ownerId, 1, Optional.empty());
|
||||||
|
PocketBaseStateManager states = mock(PocketBaseStateManager.class);
|
||||||
|
PocketBaseWorldService worlds = mock(PocketBaseWorldService.class);
|
||||||
|
when(states.state(ownerId)).thenReturn(current);
|
||||||
|
when(states.updateAndSave(org.mockito.ArgumentMatchers.eq(ownerId),
|
||||||
|
org.mockito.ArgumentMatchers.any())).thenThrow(new IOException("save failed"));
|
||||||
|
PocketBaseManager manager = new PocketBaseManager(
|
||||||
|
states,
|
||||||
|
worlds,
|
||||||
|
new PluginSettingsProvider(PluginSettings.from(Map.of()))
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThrows(IOException.class,
|
||||||
|
() -> manager.setBiome(ownerId, PocketBaseBiome.DESERT));
|
||||||
|
|
||||||
|
verify(worlds).setBiome(ownerId, 1, PocketBaseBiome.DESERT);
|
||||||
|
verify(worlds).setBiome(ownerId, 1, PocketBaseBiome.THE_VOID);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void restoresWorldSettingWhenPersistenceFails() throws Exception {
|
void restoresWorldSettingWhenPersistenceFails() throws Exception {
|
||||||
UUID ownerId = UUID.randomUUID();
|
UUID ownerId = UUID.randomUUID();
|
||||||
|
|||||||
@@ -31,9 +31,12 @@ final class PocketBaseWorldServiceTest {
|
|||||||
when(server.createWorld(any(WorldCreator.class))).thenReturn(world);
|
when(server.createWorld(any(WorldCreator.class))).thenReturn(world);
|
||||||
when(world.getUID()).thenReturn(worldId);
|
when(world.getUID()).thenReturn(worldId);
|
||||||
when(world.getName()).thenReturn("pocket");
|
when(world.getName()).thenReturn("pocket");
|
||||||
|
when(world.getMinHeight()).thenReturn(0);
|
||||||
|
when(world.getMaxHeight()).thenReturn(8);
|
||||||
PocketBaseWorldService service = new PocketBaseWorldService(
|
PocketBaseWorldService service = new PocketBaseWorldService(
|
||||||
server,
|
server,
|
||||||
new PluginSettingsProvider(PluginSettings.from(Map.of()))
|
new PluginSettingsProvider(PluginSettings.from(Map.of())),
|
||||||
|
ignored -> null
|
||||||
);
|
);
|
||||||
|
|
||||||
service.expand(ownerId, 0, 1);
|
service.expand(ownerId, 0, 1);
|
||||||
@@ -84,7 +87,8 @@ final class PocketBaseWorldServiceTest {
|
|||||||
when(world.getUID()).thenReturn(UUID.randomUUID());
|
when(world.getUID()).thenReturn(UUID.randomUUID());
|
||||||
PocketBaseWorldService service = new PocketBaseWorldService(
|
PocketBaseWorldService service = new PocketBaseWorldService(
|
||||||
server,
|
server,
|
||||||
new PluginSettingsProvider(PluginSettings.from(Map.of()))
|
new PluginSettingsProvider(PluginSettings.from(Map.of())),
|
||||||
|
ignored -> null
|
||||||
);
|
);
|
||||||
|
|
||||||
service.loadExisting(Map.of(
|
service.loadExisting(Map.of(
|
||||||
@@ -95,6 +99,32 @@ final class PocketBaseWorldServiceTest {
|
|||||||
verify(world).setSpawnFlags(true, true);
|
verify(world).setSpawnFlags(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void appliesSelectedBiomeAcrossUnlockedColumnsWithoutChangingBlocks() {
|
||||||
|
UUID ownerId = UUID.randomUUID();
|
||||||
|
Server server = mock(Server.class);
|
||||||
|
World world = mock(World.class);
|
||||||
|
when(server.getWorld(org.mockito.ArgumentMatchers.anyString())).thenReturn(world);
|
||||||
|
when(world.getUID()).thenReturn(UUID.randomUUID());
|
||||||
|
when(world.getMinHeight()).thenReturn(0);
|
||||||
|
when(world.getMaxHeight()).thenReturn(8);
|
||||||
|
PocketBaseWorldService service = new PocketBaseWorldService(
|
||||||
|
server,
|
||||||
|
new PluginSettingsProvider(PluginSettings.from(Map.of())),
|
||||||
|
ignored -> null
|
||||||
|
);
|
||||||
|
|
||||||
|
service.setBiome(ownerId, 1, PocketBaseBiome.CRIMSON_FOREST);
|
||||||
|
|
||||||
|
verify(world).setBiome(-32, 0, -32, null);
|
||||||
|
verify(world).setBiome(28, 4, 28, null);
|
||||||
|
verify(world, never()).getBlockAt(
|
||||||
|
org.mockito.ArgumentMatchers.anyInt(),
|
||||||
|
org.mockito.ArgumentMatchers.anyInt(),
|
||||||
|
org.mockito.ArgumentMatchers.anyInt()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void laterLevelAddsRingWithoutOverwritingExistingTerrain() {
|
void laterLevelAddsRingWithoutOverwritingExistingTerrain() {
|
||||||
UUID ownerId = UUID.randomUUID();
|
UUID ownerId = UUID.randomUUID();
|
||||||
@@ -104,9 +134,12 @@ final class PocketBaseWorldServiceTest {
|
|||||||
when(server.createWorld(any(WorldCreator.class))).thenReturn(world);
|
when(server.createWorld(any(WorldCreator.class))).thenReturn(world);
|
||||||
when(world.getUID()).thenReturn(UUID.randomUUID());
|
when(world.getUID()).thenReturn(UUID.randomUUID());
|
||||||
when(world.getName()).thenReturn("pocket");
|
when(world.getName()).thenReturn("pocket");
|
||||||
|
when(world.getMinHeight()).thenReturn(0);
|
||||||
|
when(world.getMaxHeight()).thenReturn(8);
|
||||||
PocketBaseWorldService service = new PocketBaseWorldService(
|
PocketBaseWorldService service = new PocketBaseWorldService(
|
||||||
server,
|
server,
|
||||||
new PluginSettingsProvider(PluginSettings.from(Map.of()))
|
new PluginSettingsProvider(PluginSettings.from(Map.of())),
|
||||||
|
ignored -> null
|
||||||
);
|
);
|
||||||
Block existingCenter = world.getBlockAt(0, 64, 0);
|
Block existingCenter = world.getBlockAt(0, 64, 0);
|
||||||
|
|
||||||
@@ -116,6 +149,8 @@ final class PocketBaseWorldServiceTest {
|
|||||||
.setType(Material.GRASS_BLOCK, false);
|
.setType(Material.GRASS_BLOCK, false);
|
||||||
verify(existingCenter, never())
|
verify(existingCenter, never())
|
||||||
.setType(any(Material.class), org.mockito.ArgumentMatchers.anyBoolean());
|
.setType(any(Material.class), org.mockito.ArgumentMatchers.anyBoolean());
|
||||||
|
verify(world).setBiome(44, 4, 44, null);
|
||||||
|
verify(world, never()).setBiome(0, 0, 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<BlockPosition, Block> blocks(World world) {
|
private static Map<BlockPosition, Block> blocks(World world) {
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ final class YamlPocketBaseRepositoryTest {
|
|||||||
Optional.of(new PocketPortalLocation(
|
Optional.of(new PocketPortalLocation(
|
||||||
worldId, "pocket", 20, 65, 12, PocketPortalAxis.Z
|
worldId, "pocket", 20, 65, 12, PocketPortalAxis.Z
|
||||||
)),
|
)),
|
||||||
true
|
true,
|
||||||
|
PocketBaseBiome.CRIMSON_FOREST
|
||||||
);
|
);
|
||||||
YamlPocketBaseRepository repository = new YamlPocketBaseRepository(
|
YamlPocketBaseRepository repository = new YamlPocketBaseRepository(
|
||||||
temporaryDirectory.resolve("pocket-bases.yml")
|
temporaryDirectory.resolve("pocket-bases.yml")
|
||||||
@@ -51,5 +52,6 @@ final class YamlPocketBaseRepositoryTest {
|
|||||||
|
|
||||||
assertFalse(loaded.mobSpawningEnabled());
|
assertFalse(loaded.mobSpawningEnabled());
|
||||||
assertTrue(loaded.returnPortal().isEmpty());
|
assertTrue(loaded.returnPortal().isEmpty());
|
||||||
|
assertEquals(PocketBaseBiome.THE_VOID, loaded.biome());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user