diff --git a/pom.xml b/pom.xml
index e3fc888..162477a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,13 +42,9 @@
- codemc-snapshots
- https://repo.codemc.org/repository/maven-snapshots
+ bentoboxworld
+ https://repo.codemc.org/repository/bentoboxworld/
-
- codemc-releases
- https://repo.codemc.org/repository/maven-releases
-
@@ -58,14 +54,14 @@
2.0.9
- 1.20.4-R0.1-SNAPSHOT
- 2.5.0-SNAPSHOT
+ 1.21.3-R0.1-SNAPSHOT
+ 2.7.1-SNAPSHOT
${build.version}-SNAPSHOT
-LOCAL
- 1.18.1
+ 1.19.0
BentoBoxWorld_BSkyBlock
bentobox-world
@@ -121,7 +117,7 @@
codemc
- https://repo.codemc.org/repository/maven-snapshots/
+ https://repo.codemc.org/repository/bentoboxworld/
codemc-repo
diff --git a/src/main/java/world/bentobox/bskyblock/BSkyBlock.java b/src/main/java/world/bentobox/bskyblock/BSkyBlock.java
index 1a485ff..6c55c44 100644
--- a/src/main/java/world/bentobox/bskyblock/BSkyBlock.java
+++ b/src/main/java/world/bentobox/bskyblock/BSkyBlock.java
@@ -2,9 +2,9 @@ package world.bentobox.bskyblock;
import org.bukkit.World;
import org.bukkit.World.Environment;
-import org.bukkit.entity.SpawnCategory;
import org.bukkit.WorldCreator;
import org.bukkit.WorldType;
+import org.bukkit.entity.SpawnCategory;
import org.bukkit.event.Listener;
import org.bukkit.generator.ChunkGenerator;
import org.eclipse.jdt.annotation.Nullable;
diff --git a/src/main/java/world/bentobox/bskyblock/Settings.java b/src/main/java/world/bentobox/bskyblock/Settings.java
index 4fc2f2b..4eb7b1b 100644
--- a/src/main/java/world/bentobox/bskyblock/Settings.java
+++ b/src/main/java/world/bentobox/bskyblock/Settings.java
@@ -1,6 +1,12 @@
package world.bentobox.bskyblock;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import org.bukkit.Difficulty;
import org.bukkit.GameMode;
@@ -8,8 +14,6 @@ import org.bukkit.block.Biome;
import org.bukkit.entity.EntityType;
import org.eclipse.jdt.annotation.NonNull;
-import com.google.common.base.Enums;
-
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.configuration.ConfigComment;
import world.bentobox.bentobox.api.configuration.ConfigEntry;
@@ -155,7 +159,7 @@ public class Settings implements WorldSettings {
private Biome defaultBiome = Biome.PLAINS;
@ConfigComment("The default biome for the nether world (this may affect what mobs can spawn)")
@ConfigEntry(path = "world.default-nether-biome")
- private Biome defaultNetherBiome = Enums.getIfPresent(Biome.class, "NETHER").or(Enums.getIfPresent(Biome.class, "NETHER_WASTES").or(Biome.BADLANDS));
+ private Biome defaultNetherBiome = Biome.NETHER_WASTES;
@ConfigComment("The default biome for the end world (this may affect what mobs can spawn)")
@ConfigEntry(path = "world.default-end-biome")
private Biome defaultEndBiome = Biome.THE_END;
diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml
index 8c9511a..957b5d3 100755
--- a/src/main/resources/addon.yml
+++ b/src/main/resources/addon.yml
@@ -1,7 +1,7 @@
name: BSkyBlock
main: world.bentobox.bskyblock.BSkyBlock
version: ${version}${build.number}
-api-version: 2.3.0
+api-version: 2.7.1
metrics: true
icon: "OAK_SAPLING"
repository: "BentoBoxWorld/BSkyBlock"
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 7705ccd..ae156f1 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -84,6 +84,11 @@ world:
# Maximum number of islands in the world. Set to -1 or 0 for unlimited.
# If the number of islands is greater than this number, it will stop players from creating islands.
max-islands: 0
+ # The number of concurrent islands a player can have in the world
+ # A value of 0 will use the BentoBox config.yml default
+ concurrent-islands: 1
+ # Disallow players to have other islands if they are in a team.
+ disallow-team-member-islands: true
# The default game mode for this world. Players will be set to this mode when they create
# a new island for example. Options are SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR
default-game-mode: SURVIVAL
@@ -147,9 +152,12 @@ world:
OBSIDIAN_SCOOPING: true
ISLAND_RESPAWN: true
CREEPER_GRIEFING: false
+ WORLD_BLOCK_EXPLODE_DAMAGE: false
+ VISITOR_KEEP_INVENTORY: false
PETS_STAY_AT_HOME: true
NATURAL_SPAWNING_OUTSIDE_RANGE: true
LIQUIDS_FLOWING_OUT: false
+ VISITOR_TRIGGER_RAID: true
REMOVE_MOBS: false
ENDER_CHEST: false
TREES_GROWING_OUTSIDE_RANGE: false
@@ -164,11 +172,13 @@ world:
PREVENT_TELEPORT_WHEN_FALLING: false
WORLD_TNT_DAMAGE: false
ENTER_EXIT_MESSAGES: true
+ ALLOW_MOVE_BOX: true
ENDERMAN_DEATH_DROP: true
OFFLINE_REDSTONE: true
REMOVE_END_EXIT_ISLAND: true
OFFLINE_GROWTH: true
ITEM_FRAME_DAMAGE: false
+ ENTITY_PORTAL_TELEPORT: false
SPAWNER_SPAWN_EGGS: true
# These are the default protection settings for new islands.
# The value is the minimum island rank required allowed to do the action
@@ -181,20 +191,26 @@ world:
# OWNER = 1000
default-island-flags:
HURT_ANIMALS: 500
+ LOOM: 500
DRAGON_EGG: 500
REDSTONE: 500
BUCKET: 500
LOCK: 0
ENDER_PEARL: 500
+ BELL_RINGING: 500
DOOR: 500
BREAK_HOPPERS: 500
FURNACE: 500
+ HURT_TAMED_ANIMALS: 500
MINECART: 500
ANVIL: 500
FISH_SCOOPING: 500
+ TRAPPED_CHEST: 500
END_PORTAL: 500
BREEDING: 500
HURT_VILLAGERS: 500
+ BOOKSHELF: 500
+ HARVEST: 500
TURTLE_EGGS: 500
FROST_WALKER: 500
COLLECT_LAVA: 500
@@ -206,23 +222,35 @@ world:
CAKE: 500
NAME_TAG: 500
ARMOR_STAND: 500
+ CHANGE_SETTINGS: 1000
+ SIGN_EDITING: 500
TRADING: 0
EGGS: 500
ITEM_DROP: 0
+ CHEST: 500
NOTE_BLOCK: 0
FLINT_AND_STEEL: 500
NETHER_PORTAL: 500
+ SCULK_SENSOR: 500
LECTERN: 500
+ GRINDSTONE: 500
+ SHULKER_BOX: 500
ITEM_PICKUP: 0
CROP_TRAMPLE: 500
DROPPER: 500
BREWING: 500
+ MOVE_BOX: 1000
TNT_PRIMING: 500
+ PARKOUR_CREATIVE: 500
COLLECT_WATER: 500
+ AXOLOTL_SCOOPING: 500
BUTTON: 500
+ COMPOSTER: 500
+ STONECUTTING: 500
FIRE_EXTINGUISH: 500
COMMAND_RANKS: 500
BEACON: 500
+ ALLAY: 500
TRAPDOOR: 500
EXPERIENCE_BOTTLE_THROWING: 500
PRESSURE_PLATE: 0
@@ -230,17 +258,22 @@ world:
DYE: 500
PLACE_BLOCKS: 500
ITEM_FRAME: 500
+ CROP_PLANTING: 500
CRAFTING: 0
ENCHANTING: 0
SHEARING: 500
+ FLOWER_POT: 500
BOAT: 500
BED: 500
SPAWN_EGGS: 500
MILKING: 0
DISPENSER: 500
+ SCULK_SHRIEKER: 500
GATE: 0
+ SMITHING: 500
EXPERIENCE_PICKUP: 500
HOPPER: 500
+ CANDLES: 500
LEASH: 500
MOUNT_INVENTORY: 500
BREAK_BLOCKS: 500
@@ -248,48 +281,30 @@ world:
CONTAINER: 500
POTION_THROWING: 500
JUKEBOX: 500
+ BARREL: 500
+ CARTOGRAPHY: 500
+ COLLECT_POWDERED_SNOW: 500
# These are the default settings for new islands
default-island-settings:
PVP_END: false
PVP_NETHER: false
LEAF_DECAY: true
- TNT_DAMAGE: true
- MONSTER_SPAWNERS_SPAWN: true
+ ENDERMAN_TELEPORT: true
ANIMAL_NATURAL_SPAWN: true
MONSTER_NATURAL_SPAWN: true
- FIRE_IGNITE: true
+ SHULKER_TELEPORT: true
FIRE_SPREAD: true
- ANIMAL_SPAWNERS_SPAWN: true
FIRE_BURNING: true
PVP_OVERWORLD: false
+ TNT_DAMAGE: true
+ MONSTER_SPAWNERS_SPAWN: true
+ FIRE_IGNITE: true
+ ANIMAL_SPAWNERS_SPAWN: true
+ BLOCK_EXPLODE_DAMAGE: true
# These settings/flags are hidden from users
# Ops can toggle hiding in-game using SHIFT-LEFT-CLICK on flags in settings
# Added since 1.4.1.
- hidden-flags:
- - CHEST_DAMAGE
- - HURT_MONSTERS
- - BREAK_SPAWNERS
- - DISPENSER
- - DRAGON_EGG
- - DROPPER
- - EXPERIENCE_BOTTLE_THROWING
- - HURT_VILLAGERS
- - MOUNT_INVENTORY
- - NOTE_BLOCK
- - TURTLE_EGGS
- - PVP_END
- - FIRE_BURNING
- - FIRE_IGNITE
- - FIRE_SPREAD
- - PVP_NETHER
- - PVP_OVERWORLD
- - TNT_DAMAGE
- - MONSTER_NATURAL_SPAWN
- - MONSTER_SPAWNERS_SPAWN
- - ANIMAL_SPAWNERS_SPAWN
- - ANIMAL_NATURAL_SPAWN
- - LEAF_DECAY
- - BREAK_HOPPERS
+ hidden-flags: []
# Visitor banned commands - Visitors to islands cannot use these commands in this world
visitor-banned-commands:
- spawner
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index d84f487..5b78ade 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,7 +1,7 @@
name: BentoBox-BSkyBlock
main: world.bentobox.bskyblock.BSkyBlockPladdon
version: ${project.version}${build.number}
-api-version: "1.19"
+api-version: "1.21"
authors: [tastybento]
contributors: ["The BentoBoxWorld Community"]
diff --git a/src/test/java/world/bentobox/bskyblock/BSkyBlockTest.java b/src/test/java/world/bentobox/bskyblock/BSkyBlockTest.java
index 9254a84..6264a11 100644
--- a/src/test/java/world/bentobox/bskyblock/BSkyBlockTest.java
+++ b/src/test/java/world/bentobox/bskyblock/BSkyBlockTest.java
@@ -30,6 +30,7 @@ import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.Server;
+import org.bukkit.UnsafeValues;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.junit.After;
@@ -59,6 +60,7 @@ import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bskyblock.generators.ChunkGeneratorWorld;
+import world.bentobox.bskyblock.mocks.ServerMocks;
/**
* @author tastybento
@@ -101,6 +103,7 @@ public class BSkyBlockTest {
@After
public void tearDown() throws IOException {
+ ServerMocks.unsetBukkitServer();
User.clearUsers();
Mockito.framework().clearInlineMocks();
deleteAll(new File("database"));
@@ -123,6 +126,7 @@ public class BSkyBlockTest {
*/
@Before
public void setUp() throws Exception {
+ ServerMocks.newServer();
// Set up plugin
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(plugin.getLogger()).thenReturn(Logger.getAnonymousLogger());
@@ -160,6 +164,9 @@ public class BSkyBlockTest {
when(Bukkit.getServer()).thenReturn(server);
when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger());
when(Bukkit.getPluginManager()).thenReturn(mock(PluginManager.class));
+ @SuppressWarnings("deprecation")
+ UnsafeValues unsafe = mock(UnsafeValues.class);
+ when(Bukkit.getUnsafe()).thenReturn(unsafe);
// Addon
addon = new BSkyBlock();
diff --git a/src/test/java/world/bentobox/bskyblock/SettingsTest.java b/src/test/java/world/bentobox/bskyblock/SettingsTest.java
index 30a11e9..0acdccb 100644
--- a/src/test/java/world/bentobox/bskyblock/SettingsTest.java
+++ b/src/test/java/world/bentobox/bskyblock/SettingsTest.java
@@ -13,9 +13,12 @@ import org.bukkit.Difficulty;
import org.bukkit.GameMode;
import org.bukkit.block.Biome;
import org.bukkit.entity.EntityType;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import world.bentobox.bskyblock.mocks.ServerMocks;
+
/**
* @author tastybento
*
@@ -29,9 +32,15 @@ public class SettingsTest {
*/
@Before
public void setUp() throws Exception {
+ ServerMocks.newServer();
s = new Settings();
}
+ @After
+ public void tearDown() {
+ ServerMocks.unsetBukkitServer();
+ }
+
/**
* Test method for {@link world.bentobox.bskyblock.Settings#setFriendlyName(java.lang.String)}.
*/
diff --git a/src/test/java/world/bentobox/bskyblock/generators/ChunkGeneratorWorldTest.java b/src/test/java/world/bentobox/bskyblock/generators/ChunkGeneratorWorldTest.java
index 9dfd720..0e2e3d6 100644
--- a/src/test/java/world/bentobox/bskyblock/generators/ChunkGeneratorWorldTest.java
+++ b/src/test/java/world/bentobox/bskyblock/generators/ChunkGeneratorWorldTest.java
@@ -17,6 +17,7 @@ import java.util.Random;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
+import org.bukkit.UnsafeValues;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.generator.ChunkGenerator.BiomeGrid;
@@ -32,6 +33,7 @@ import org.powermock.modules.junit4.PowerMockRunner;
import world.bentobox.bskyblock.BSkyBlock;
import world.bentobox.bskyblock.Settings;
+import world.bentobox.bskyblock.mocks.ServerMocks;
/**
* @author tastybento
@@ -47,9 +49,9 @@ public class ChunkGeneratorWorldTest {
@Mock
private World world;
private final Random random = new Random();
+ @SuppressWarnings("deprecation")
@Mock
private BiomeGrid biomeGrid;
- @Mock
private Settings settings;
@Mock
private ChunkData data;
@@ -57,13 +59,18 @@ public class ChunkGeneratorWorldTest {
/**
* @throws java.lang.Exception
*/
+ @SuppressWarnings("deprecation")
@Before
public void setUp() throws Exception {
+ ServerMocks.newServer();
// Bukkit
+
PowerMockito.mockStatic(Bukkit.class);
Server server = mock(Server.class);
when(server.createChunkData(any())).thenReturn(data);
when(Bukkit.getServer()).thenReturn(server);
+ UnsafeValues unsafe = mock(UnsafeValues.class);
+ when(Bukkit.getUnsafe()).thenReturn(unsafe);
// Instance
cg = new ChunkGeneratorWorld(addon);
@@ -71,34 +78,28 @@ public class ChunkGeneratorWorldTest {
when(world.getEnvironment()).thenReturn(World.Environment.NORMAL);
when(world.getMaxHeight()).thenReturn(16);
// Settings
+ settings = new Settings();
when(addon.getSettings()).thenReturn(settings);
- when(settings.getSeaHeight()).thenReturn(0);
- when(settings.isNetherRoof()).thenReturn(true);
- when(settings.getDefaultBiome()).thenReturn(Biome.TAIGA);
- when(settings.getDefaultNetherBiome()).thenReturn(Biome.CRIMSON_FOREST);
- when(settings.getDefaultEndBiome()).thenReturn(Biome.END_MIDLANDS);
}
/**
* @throws java.lang.Exception
*/
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
+ ServerMocks.unsetBukkitServer();
}
/**
* Test method for {@link world.bentobox.bskyblock.generators.ChunkGeneratorWorld#generateChunkData(org.bukkit.World, java.util.Random, int, int, org.bukkit.generator.ChunkGenerator.BiomeGrid)}.
*/
+ @SuppressWarnings("deprecation")
@Test
public void testGenerateChunkDataWorldRandomIntIntBiomeGridOverworldVoid() {
ChunkData cd = cg.generateChunkData(world, random, 0 , 0 , biomeGrid);
assertEquals(data, cd);
// Verifications
- // Default biome
- verify(settings).getDefaultBiome();
verify(biomeGrid, times(64)).setBiome(anyInt(), anyInt(), anyInt(), any());
- // Sea height
- verify(settings).getSeaHeight();
// Void
verify(cd, never()).setRegion(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(Material.class));
}
@@ -106,18 +107,15 @@ public class ChunkGeneratorWorldTest {
/**
* Test method for {@link world.bentobox.bskyblock.generators.ChunkGeneratorWorld#generateChunkData(org.bukkit.World, java.util.Random, int, int, org.bukkit.generator.ChunkGenerator.BiomeGrid)}.
*/
+ @SuppressWarnings("deprecation")
@Test
public void testGenerateChunkDataWorldRandomIntIntBiomeGridOverworldSea() {
// Set sea height
- when(settings.getSeaHeight()).thenReturn(10);
- ChunkData cd = cg.generateChunkData(world, random, 0 , 0 , biomeGrid);
+ settings.setSeaHeight(10);
+ ChunkData cd = cg.generateChunkData(world, random, 0, 0, biomeGrid);
assertEquals(data, cd);
// Verifications
- // Default biome
- verify(settings).getDefaultBiome();
verify(biomeGrid, times(64)).setBiome(anyInt(), anyInt(), anyInt(), eq(Biome.TAIGA));
- // Sea height
- verify(settings, times(2)).getSeaHeight();
// Water. Blocks = 16 x 16 x 11 because block 0
verify(cd).setRegion(0, 0, 0, 16, 11, 16, Material.WATER);
}
@@ -125,18 +123,15 @@ public class ChunkGeneratorWorldTest {
/**
* Test method for {@link world.bentobox.bskyblock.generators.ChunkGeneratorWorld#generateChunkData(org.bukkit.World, java.util.Random, int, int, org.bukkit.generator.ChunkGenerator.BiomeGrid)}.
*/
+ @SuppressWarnings("deprecation")
@Test
public void testGenerateChunkDataWorldRandomIntIntBiomeGridEnd() {
when(world.getEnvironment()).thenReturn(World.Environment.THE_END);
ChunkData cd = cg.generateChunkData(world, random, 0 , 0 , biomeGrid);
assertEquals(data, cd);
// Verifications
- // Default biome
- verify(settings).getDefaultEndBiome();
// Set biome in end
verify(biomeGrid, times(64)).setBiome(anyInt(), anyInt(), anyInt(), eq(Biome.END_MIDLANDS));
- // Sea height
- verify(settings, never()).getSeaHeight();
// Void
verify(cd, never()).setRegion(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(Material.class));
}
@@ -144,14 +139,13 @@ public class ChunkGeneratorWorldTest {
/**
* Test method for {@link world.bentobox.bskyblock.generators.ChunkGeneratorWorld#generateChunkData(org.bukkit.World, java.util.Random, int, int, org.bukkit.generator.ChunkGenerator.BiomeGrid)}.
*/
+ @SuppressWarnings("deprecation")
@Test
public void testGenerateChunkDataWorldRandomIntIntBiomeGridNetherWithRoof() {
when(world.getEnvironment()).thenReturn(World.Environment.NETHER);
ChunkData cd = cg.generateChunkData(world, random, 0 , 0 , biomeGrid);
assertEquals(data, cd);
// Verifications
- // Nether roof check
- verify(settings).isNetherRoof();
// Set biome in nether
verify(biomeGrid, times(64)).setBiome(anyInt(), anyInt(), anyInt(), eq(Biome.CRIMSON_FOREST));
// Nether roof - at least bedrock layer
@@ -161,16 +155,14 @@ public class ChunkGeneratorWorldTest {
/**
* Test method for {@link world.bentobox.bskyblock.generators.ChunkGeneratorWorld#generateChunkData(org.bukkit.World, java.util.Random, int, int, org.bukkit.generator.ChunkGenerator.BiomeGrid)}.
*/
+ @SuppressWarnings("deprecation")
@Test
public void testGenerateChunkDataWorldRandomIntIntBiomeGridNetherNoRoof() {
- when(settings.isNetherRoof()).thenReturn(false);
+ settings.setNetherRoof(false);
when(world.getEnvironment()).thenReturn(World.Environment.NETHER);
ChunkData cd = cg.generateChunkData(world, random, 0 , 0 , biomeGrid);
assertEquals(data, cd);
// Verifications
- verify(settings).getDefaultNetherBiome();
- // Nether roof check
- verify(settings).isNetherRoof();
// Set biome in nether
verify(biomeGrid, times(64)).setBiome(anyInt(), anyInt(), anyInt(), eq(Biome.CRIMSON_FOREST));
// Nether roof - at least bedrock layer
diff --git a/src/test/java/world/bentobox/bskyblock/mocks/ServerMocks.java b/src/test/java/world/bentobox/bskyblock/mocks/ServerMocks.java
new file mode 100644
index 0000000..4f0d4d1
--- /dev/null
+++ b/src/test/java/world/bentobox/bskyblock/mocks/ServerMocks.java
@@ -0,0 +1,118 @@
+package world.bentobox.bskyblock.mocks;
+
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.bukkit.Server;
+import org.bukkit.Tag;
+import org.bukkit.UnsafeValues;
+import org.eclipse.jdt.annotation.NonNull;
+
+public final class ServerMocks {
+
+ public static @NonNull Server newServer() {
+ Server mock = mock(Server.class);
+
+ Logger noOp = mock(Logger.class);
+ when(mock.getLogger()).thenReturn(noOp);
+ when(mock.isPrimaryThread()).thenReturn(true);
+
+ // Unsafe
+ UnsafeValues unsafe = mock(UnsafeValues.class);
+ when(mock.getUnsafe()).thenReturn(unsafe);
+
+ // Server must be available before tags can be mocked.
+ Bukkit.setServer(mock);
+
+ // Bukkit has a lot of static constants referencing registry values. To initialize those, the
+ // registries must be able to be fetched before the classes are touched.
+ Map, Object> registers = new HashMap<>();
+
+ doAnswer(invocationGetRegistry -> registers.computeIfAbsent(invocationGetRegistry.getArgument(0), clazz -> {
+ Registry> registry = mock(Registry.class);
+ Map cache = new HashMap<>();
+ doAnswer(invocationGetEntry -> {
+ NamespacedKey key = invocationGetEntry.getArgument(0);
+ // Some classes (like BlockType and ItemType) have extra generics that will be
+ // erased during runtime calls. To ensure accurate typing, grab the constant's field.
+ // This approach also allows us to return null for unsupported keys.
+ Class extends Keyed> constantClazz;
+ try {
+ //noinspection unchecked
+ constantClazz = (Class extends Keyed>) clazz
+ .getField(key.getKey().toUpperCase(Locale.ROOT).replace('.', '_')).getType();
+ } catch (ClassCastException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchFieldException e) {
+ return null;
+ }
+
+ return cache.computeIfAbsent(key, key1 -> {
+ Keyed keyed = mock(constantClazz);
+ doReturn(key).when(keyed).getKey();
+ return keyed;
+ });
+ }).when(registry).get(notNull());
+ return registry;
+ })).when(mock).getRegistry(notNull());
+
+ // Tags are dependent on registries, but use a different method.
+ // This will set up blank tags for each constant; all that needs to be done to render them
+ // functional is to re-mock Tag#getValues.
+ doAnswer(invocationGetTag -> {
+ Tag> tag = mock(Tag.class);
+ doReturn(invocationGetTag.getArgument(1)).when(tag).getKey();
+ doReturn(Set.of()).when(tag).getValues();
+ doAnswer(invocationIsTagged -> {
+ Keyed keyed = invocationIsTagged.getArgument(0);
+ Class> type = invocationGetTag.getArgument(2);
+ if (!type.isAssignableFrom(keyed.getClass())) {
+ return null;
+ }
+ // Since these are mocks, the exact instance might not be equal. Consider equal keys equal.
+ return tag.getValues().contains(keyed)
+ || tag.getValues().stream().anyMatch(value -> value.getKey().equals(keyed.getKey()));
+ }).when(tag).isTagged(notNull());
+ return tag;
+ }).when(mock).getTag(notNull(), notNull(), notNull());
+
+ // Once the server is all set up, touch BlockType and ItemType to initialize.
+ // This prevents issues when trying to access dependent methods from a Material constant.
+ try {
+ Class.forName("org.bukkit.inventory.ItemType");
+ Class.forName("org.bukkit.block.BlockType");
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ return mock;
+ }
+
+ public static void unsetBukkitServer() {
+ try {
+ Field server = Bukkit.class.getDeclaredField("server");
+ server.setAccessible(true);
+ server.set(null, null);
+ } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private ServerMocks() {
+ }
+
+}
\ No newline at end of file