From 252c2b4c01ccf2b4992a64a75ba0923311ad0db4 Mon Sep 17 00:00:00 2001 From: Andreas Troelsen Date: Sun, 16 May 2021 21:50:18 +0200 Subject: [PATCH] Introducing MobArena Labs. This commit introduces the concept of MobArena Labs as well as the first explicitly experimental feature in the form of custom entity type lists for the cleanup procedure. MobArena Labs is an attempt at a compromise between the otherwise rigid approach to functionality due to maintainability concerns and some of the esoteric feature requests we see on Github. The compromise manifests itself as follows: - All experimental functionality is opt-in and set up primarily in a new file, `labs.yml`, which must be created manually. Nothing is provided for users by default, so the "I know what I'm doing"-attitude required for this functionality to work hopefully helps convey the user-facing downside; that experimental functionality is much more likely to break between releases, and that compatibility issues are ignored. - The other side of the coin is that we can try new things as long as they are _local to MobArena_, i.e. no external dependencies. Some of the esoteric and niche feature requests we see on Github could make it into the plugin as experimental features. If enough servers make use of them and provide feedback, they could end up as regular features with stability and robustness concerns on par with core parts of the plugin. We use bStats to help visualize feature usage (or lack thereof so we can remove unused functionality if it is problematic). The first experimental feature is the ability to customize the list of entity types that MobArena looks for when clearing the arena region of residual entities such as arrows and experience orbs. This is called the Housekeeper, and it has its own section in `labs.yml`. It can be toggled on or off with the `enabled` flag, and the list of entities is specified with the `entities` list. Because the original code was hardcoded in the ArenaImpl class, no effort has been made to make the Housekeeper and its settings per-arena configurable. Closes #667 --- .../com/garbagemule/MobArena/ArenaImpl.java | 42 +------- .../com/garbagemule/MobArena/MobArena.java | 20 ++++ .../MobArena/housekeeper/BlockCleaner.java | 24 +++++ .../housekeeper/CompositeHousekeeper.java | 21 ++++ .../MobArena/housekeeper/EntityCleaner.java | 76 +++++++++++++ .../MobArena/housekeeper/Housekeeper.java | 9 ++ .../housekeeper/HousekeeperConfig.java | 75 +++++++++++++ .../MobArena/housekeeper/Housekeepers.java | 25 +++++ .../MobArena/housekeeper/MonsterCleaner.java | 22 ++++ .../com/garbagemule/MobArena/labs/Labs.java | 77 +++++++++++++ .../garbagemule/MobArena/labs/LabsChart.java | 35 ++++++ .../garbagemule/MobArena/labs/LabsConfig.java | 42 ++++++++ .../MobArena/labs/LabsConfigSection.java | 11 ++ .../com/garbagemule/MobArena/util/Enums.java | 8 ++ .../housekeeper/BlockCleanerTest.java | 36 +++++++ .../housekeeper/EntityCleanerTest.java | 102 ++++++++++++++++++ .../housekeeper/MonsterCleanerTest.java | 26 +++++ 17 files changed, 614 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/garbagemule/MobArena/housekeeper/BlockCleaner.java create mode 100644 src/main/java/com/garbagemule/MobArena/housekeeper/CompositeHousekeeper.java create mode 100644 src/main/java/com/garbagemule/MobArena/housekeeper/EntityCleaner.java create mode 100644 src/main/java/com/garbagemule/MobArena/housekeeper/Housekeeper.java create mode 100644 src/main/java/com/garbagemule/MobArena/housekeeper/HousekeeperConfig.java create mode 100644 src/main/java/com/garbagemule/MobArena/housekeeper/Housekeepers.java create mode 100644 src/main/java/com/garbagemule/MobArena/housekeeper/MonsterCleaner.java create mode 100644 src/main/java/com/garbagemule/MobArena/labs/Labs.java create mode 100644 src/main/java/com/garbagemule/MobArena/labs/LabsChart.java create mode 100644 src/main/java/com/garbagemule/MobArena/labs/LabsConfig.java create mode 100644 src/main/java/com/garbagemule/MobArena/labs/LabsConfigSection.java create mode 100644 src/test/java/com/garbagemule/MobArena/housekeeper/BlockCleanerTest.java create mode 100644 src/test/java/com/garbagemule/MobArena/housekeeper/EntityCleanerTest.java create mode 100644 src/test/java/com/garbagemule/MobArena/housekeeper/MonsterCleanerTest.java diff --git a/src/main/java/com/garbagemule/MobArena/ArenaImpl.java b/src/main/java/com/garbagemule/MobArena/ArenaImpl.java index 34d9e98..2bb0863 100644 --- a/src/main/java/com/garbagemule/MobArena/ArenaImpl.java +++ b/src/main/java/com/garbagemule/MobArena/ArenaImpl.java @@ -6,6 +6,7 @@ import com.garbagemule.MobArena.ScoreboardManager.NullScoreboardManager; import com.garbagemule.MobArena.announce.Announcer; import com.garbagemule.MobArena.announce.MessengerAnnouncer; import com.garbagemule.MobArena.announce.TitleAnnouncer; +import com.garbagemule.MobArena.housekeeper.Housekeeper; import com.garbagemule.MobArena.steps.Step; import com.garbagemule.MobArena.steps.StepFactory; import com.garbagemule.MobArena.steps.PlayerJoinArena; @@ -35,7 +36,6 @@ import com.garbagemule.MobArena.waves.SheepBouncer; import com.garbagemule.MobArena.waves.WaveManager; import org.bukkit.Bukkit; import org.bukkit.ChatColor; -import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; @@ -148,6 +148,7 @@ public class ArenaImpl implements Arena private StepFactory playerSpecArena; private SpawnsPets spawnsPets; + private Housekeeper housekeeper; /** * Primary constructor. Requires a name and a world. @@ -257,6 +258,8 @@ public class ArenaImpl implements Arena this.playerSpecArena = PlayerSpecArena.create(this); this.spawnsPets = plugin.getArenaMaster().getSpawnsPets(); + + this.housekeeper = plugin.getLabs().housekeeper; } @@ -1355,45 +1358,10 @@ public class ArenaImpl implements Arena } private void cleanup() { - removeMonsters(); - removeBlocks(); - removeEntities(); + housekeeper.clean(this); clearPlayers(); } - private void removeMonsters() { - monsterManager.clear(); - } - - private void removeBlocks() { - for (Block b : blocks) { - b.setType(Material.AIR); - } - blocks.clear(); - } - - private void removeEntities() { - List chunks = region.getChunks(); - - for (Chunk c : chunks) { - for (Entity e : c.getEntities()) { - if (e == null) { - continue; - } - - switch (e.getType()) { - case DROPPED_ITEM: - case EXPERIENCE_ORB: - case ARROW: - case MINECART: - case BOAT: - case SHULKER_BULLET: - e.remove(); - } - } - } - } - private void clearPlayers() { arenaPlayers.clear(); arenaPlayerMap.clear(); diff --git a/src/main/java/com/garbagemule/MobArena/MobArena.java b/src/main/java/com/garbagemule/MobArena/MobArena.java index 6bd10e4..4a1a800 100644 --- a/src/main/java/com/garbagemule/MobArena/MobArena.java +++ b/src/main/java/com/garbagemule/MobArena/MobArena.java @@ -8,6 +8,8 @@ import com.garbagemule.MobArena.formula.FormulaMacros; import com.garbagemule.MobArena.formula.FormulaManager; import com.garbagemule.MobArena.framework.Arena; import com.garbagemule.MobArena.framework.ArenaMaster; +import com.garbagemule.MobArena.labs.Labs; +import com.garbagemule.MobArena.labs.LabsChart; import com.garbagemule.MobArena.listeners.MAGlobalListener; import com.garbagemule.MobArena.metrics.ArenaCountChart; import com.garbagemule.MobArena.metrics.ClassChestsChart; @@ -69,6 +71,8 @@ public class MobArena extends JavaPlugin private SignListeners signListeners; + private Labs labs; + @Override public void onLoad() { thingman = new ThingManager(this); @@ -180,6 +184,8 @@ public class MobArena extends JavaPlugin metrics.addCustomChart(new IsolatedChatChart(this)); metrics.addCustomChart(new MonsterInfightChart(this)); metrics.addCustomChart(new PvpEnabledChart(this)); + + metrics.addCustomChart(new LabsChart(this, "labs_housekeeper_pie", config -> config.housekeeper)); } public void reload() { @@ -188,6 +194,7 @@ public class MobArena extends JavaPlugin try { reloadConfig(); + reloadLabs(); reloadGlobalMessenger(); reloadFormulaMacros(); reloadArenaMaster(); @@ -210,6 +217,15 @@ public class MobArena extends JavaPlugin config = loadsConfigFile.load(); } + private void reloadLabs() { + try { + labs = Labs.create(this); + } catch (IOException e) { + getLogger().log(Level.WARNING, "There was an error loading MobArena Labs!", e); + labs = Labs.createDefault(); + } + } + private void reloadGlobalMessenger() { String prefix = config.getString("global-settings.prefix", ""); if (prefix.isEmpty()) { @@ -317,4 +333,8 @@ public class MobArena extends JavaPlugin public FormulaMacros getFormulaMacros() { return macros; } + + public Labs getLabs() { + return labs; + } } diff --git a/src/main/java/com/garbagemule/MobArena/housekeeper/BlockCleaner.java b/src/main/java/com/garbagemule/MobArena/housekeeper/BlockCleaner.java new file mode 100644 index 0000000..1a3d77e --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/housekeeper/BlockCleaner.java @@ -0,0 +1,24 @@ +package com.garbagemule.MobArena.housekeeper; + +import com.garbagemule.MobArena.framework.Arena; +import org.bukkit.Material; + +class BlockCleaner implements Housekeeper { + + private static final BlockCleaner DEFAULT = new BlockCleaner(); + + private BlockCleaner() { + // OK BOSS + } + + @Override + public void clean(Arena arena) { + arena.getBlocks().forEach(block -> block.setType(Material.AIR)); + arena.getBlocks().clear(); + } + + static BlockCleaner getDefault() { + return DEFAULT; + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/housekeeper/CompositeHousekeeper.java b/src/main/java/com/garbagemule/MobArena/housekeeper/CompositeHousekeeper.java new file mode 100644 index 0000000..4eaa260 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/housekeeper/CompositeHousekeeper.java @@ -0,0 +1,21 @@ +package com.garbagemule.MobArena.housekeeper; + +import com.garbagemule.MobArena.framework.Arena; + +import java.util.Arrays; +import java.util.List; + +class CompositeHousekeeper implements Housekeeper { + + private final List minions; + + CompositeHousekeeper(Housekeeper... minions) { + this.minions = Arrays.asList(minions); + } + + @Override + public void clean(Arena arena) { + minions.forEach(minion -> minion.clean(arena)); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/housekeeper/EntityCleaner.java b/src/main/java/com/garbagemule/MobArena/housekeeper/EntityCleaner.java new file mode 100644 index 0000000..107562a --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/housekeeper/EntityCleaner.java @@ -0,0 +1,76 @@ +package com.garbagemule.MobArena.housekeeper; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.region.ArenaRegion; +import org.bukkit.Chunk; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; + +import java.util.EnumSet; +import java.util.Objects; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +class EntityCleaner implements Housekeeper { + + private static final EntityCleaner DEFAULT = new EntityCleaner(EnumSet.of( + EntityType.ARROW, + EntityType.BOAT, + EntityType.DROPPED_ITEM, + EntityType.EXPERIENCE_ORB, + EntityType.MINECART, + EntityType.SHULKER_BULLET + )); + + private final EnumSet entities; + + EntityCleaner(EnumSet entities) { + this.entities = entities; + } + + @Override + public void clean(Arena arena) { + ArenaRegion region = arena.getRegion(); + + for (Chunk chunk : region.getChunks()) { + for (Entity entity : chunk.getEntities()) { + if (entity == null) { + continue; + } + if (entities.contains(entity.getType())) { + entity.remove(); + } + } + } + } + + static EntityCleaner getDefault() { + return DEFAULT; + } + + static EntityCleaner create(HousekeeperConfig config, Logger log) { + if (config.entities == null || config.entities.isEmpty()) { + return EntityCleaner.getDefault(); + } + + EnumSet entities = config.entities + .stream() + .map(value -> parse(value, log)) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(EntityType.class))); + + return new EntityCleaner(entities); + } + + private static EntityType parse(String value, Logger log) { + try { + return EntityType.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + if (log != null) { + log.warning("Unknown housekeeper entity type '" + value + "', skipping..."); + } + return null; + } + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/housekeeper/Housekeeper.java b/src/main/java/com/garbagemule/MobArena/housekeeper/Housekeeper.java new file mode 100644 index 0000000..7e7a0f8 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/housekeeper/Housekeeper.java @@ -0,0 +1,9 @@ +package com.garbagemule.MobArena.housekeeper; + +import com.garbagemule.MobArena.framework.Arena; + +public interface Housekeeper { + + void clean(Arena arena); + +} diff --git a/src/main/java/com/garbagemule/MobArena/housekeeper/HousekeeperConfig.java b/src/main/java/com/garbagemule/MobArena/housekeeper/HousekeeperConfig.java new file mode 100644 index 0000000..13709f2 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/housekeeper/HousekeeperConfig.java @@ -0,0 +1,75 @@ +package com.garbagemule.MobArena.housekeeper; + +import com.garbagemule.MobArena.labs.LabsConfigSection; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class HousekeeperConfig extends LabsConfigSection { + + public final Set entities; + + private HousekeeperConfig(boolean enabled, Set entities) { + super(enabled); + this.entities = Collections.unmodifiableSet(entities); + } + + public static HousekeeperConfig parse(Map section) { + boolean enabled = parseEnabled(section); + Set entities = parseEntities(section); + + return new HousekeeperConfig(enabled, entities); + } + + private static boolean parseEnabled(Map section) { + if (section == null) { + return false; + } + + Object raw = section.get("enabled"); + if (raw == null) { + return false; + } + + if (raw instanceof Boolean) { + return (Boolean) raw; + } + if (raw instanceof String) { + return Boolean.parseBoolean((String) raw); + } + + throw new IllegalArgumentException("Unexpected 'enabled' value in housekeeper config"); + } + + private static Set parseEntities(Map section) { + if (section == null) { + return Collections.emptySet(); + } + + Object raw = section.get("entities"); + if (raw == null) { + return Collections.emptySet(); + } + + if (raw instanceof List) { + return ((List) raw) + .stream() + .map(String::valueOf) + .collect(Collectors.toSet()); + } + if (raw instanceof String) { + String value = (String) raw; + String[] parts = value.split(","); + return Arrays.stream(parts) + .map(String::trim) + .collect(Collectors.toSet()); + } + + throw new IllegalArgumentException("Unexpected 'entities' value in housekeeper config"); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/housekeeper/Housekeepers.java b/src/main/java/com/garbagemule/MobArena/housekeeper/Housekeepers.java new file mode 100644 index 0000000..9296bec --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/housekeeper/Housekeepers.java @@ -0,0 +1,25 @@ +package com.garbagemule.MobArena.housekeeper; + +import java.util.logging.Logger; + +public final class Housekeepers { + + private static final Housekeeper DEFAULT = new CompositeHousekeeper( + MonsterCleaner.getDefault(), + BlockCleaner.getDefault(), + EntityCleaner.getDefault() + ); + + public static Housekeeper getDefault() { + return DEFAULT; + } + + public static Housekeeper create(HousekeeperConfig config, Logger log) { + MonsterCleaner monsters = MonsterCleaner.getDefault(); + BlockCleaner blocks = BlockCleaner.getDefault(); + EntityCleaner entities = EntityCleaner.create(config, log); + + return new CompositeHousekeeper(monsters, blocks, entities); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/housekeeper/MonsterCleaner.java b/src/main/java/com/garbagemule/MobArena/housekeeper/MonsterCleaner.java new file mode 100644 index 0000000..1a189c1 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/housekeeper/MonsterCleaner.java @@ -0,0 +1,22 @@ +package com.garbagemule.MobArena.housekeeper; + +import com.garbagemule.MobArena.framework.Arena; + +class MonsterCleaner implements Housekeeper { + + private static final MonsterCleaner DEFAULT = new MonsterCleaner(); + + private MonsterCleaner() { + // OK BOSS + } + + @Override + public void clean(Arena arena) { + arena.getMonsterManager().clear(); + } + + static MonsterCleaner getDefault() { + return DEFAULT; + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/labs/Labs.java b/src/main/java/com/garbagemule/MobArena/labs/Labs.java new file mode 100644 index 0000000..584493c --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/labs/Labs.java @@ -0,0 +1,77 @@ +package com.garbagemule.MobArena.labs; + +import com.garbagemule.MobArena.MobArena; +import com.garbagemule.MobArena.housekeeper.Housekeeper; +import com.garbagemule.MobArena.housekeeper.HousekeeperConfig; +import com.garbagemule.MobArena.housekeeper.Housekeepers; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Map; +import java.util.logging.Logger; + +public class Labs { + + public final LabsConfig config; + public final Housekeeper housekeeper; + + private Labs( + LabsConfig config, + Housekeeper housekeeper + ) { + this.config = config; + this.housekeeper = housekeeper; + } + + public static Labs create(MobArena plugin) throws IOException { + Path labsFile = plugin.getDataFolder().toPath().resolve("labs.yml"); + if (!Files.exists(labsFile)) { + return createDefault(); + } + + Logger log = plugin.getLogger(); + String[] lines = { + "---==[ MobArena Labs ]==---", + "Labs is a set of experimental opt-in features that are exempt from", + "the goals of stability and robustness that are usually imposed on", + "functionality in the plugin. This means that breaking changes are", + "to be expected. No effort is made to ensure compatibility between", + "different iterations of Labs features.", + }; + Arrays.stream(lines).forEach(log::info); + + Yaml yaml = new Yaml(); + byte[] bytes = Files.readAllBytes(labsFile); + Map map = yaml.load(new String(bytes)); + + LabsConfig config = LabsConfig.parse(map); + Housekeeper housekeeper = createHousekeeper(config, log); + + log.info("---"); + + return new Labs(config, housekeeper); + } + + private static Housekeeper createHousekeeper(LabsConfig root, Logger log) { + HousekeeperConfig config = root.housekeeper; + if (config == null || !config.enabled) { + return Housekeepers.getDefault(); + } + + Housekeeper housekeeper = Housekeepers.create(config, log); + log.info("Custom housekeeper created."); + + return housekeeper; + } + + public static Labs createDefault() { + return new Labs( + LabsConfig.parse(null), + Housekeepers.getDefault() + ); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/labs/LabsChart.java b/src/main/java/com/garbagemule/MobArena/labs/LabsChart.java new file mode 100644 index 0000000..4f120b8 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/labs/LabsChart.java @@ -0,0 +1,35 @@ +package com.garbagemule.MobArena.labs; + +import com.garbagemule.MobArena.MobArena; +import org.bstats.charts.SimplePie; + +import java.util.function.Function; + +public class LabsChart extends SimplePie { + + public LabsChart( + MobArena plugin, + String chartId, + Function getter + ) { + super(chartId, () -> usesFeature(plugin, getter) ? "Yes" : "No"); + } + + private static boolean usesFeature( + MobArena plugin, + Function getter + ) { + Labs labs = plugin.getLabs(); + if (labs == null) { + return false; + } + + LabsConfigSection section = getter.apply(labs.config); + if (section == null) { + return false; + } + + return section.enabled; + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/labs/LabsConfig.java b/src/main/java/com/garbagemule/MobArena/labs/LabsConfig.java new file mode 100644 index 0000000..8b05528 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/labs/LabsConfig.java @@ -0,0 +1,42 @@ +package com.garbagemule.MobArena.labs; + +import com.garbagemule.MobArena.housekeeper.HousekeeperConfig; + +import java.util.Map; +import java.util.function.Function; + +public class LabsConfig { + + public final HousekeeperConfig housekeeper; + + LabsConfig( + HousekeeperConfig housekeeper + ) { + this.housekeeper = housekeeper; + } + + static LabsConfig parse(Map root) { + return new LabsConfig( + parse(root, "housekeeper", HousekeeperConfig::parse) + ); + } + + @SuppressWarnings("SameParameterValue") + private static C parse( + Map root, + String key, + Function, C> parse + ) { + if (root == null) { + return parse.apply(null); + } + + Object raw = root.get(key); + + @SuppressWarnings("unchecked") + Map section = (Map) raw; + + return parse.apply(section); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/labs/LabsConfigSection.java b/src/main/java/com/garbagemule/MobArena/labs/LabsConfigSection.java new file mode 100644 index 0000000..6a4a555 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/labs/LabsConfigSection.java @@ -0,0 +1,11 @@ +package com.garbagemule.MobArena.labs; + +public abstract class LabsConfigSection { + + public final boolean enabled; + + protected LabsConfigSection(boolean enabled) { + this.enabled = enabled; + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/util/Enums.java b/src/main/java/com/garbagemule/MobArena/util/Enums.java index f966456..58eef33 100644 --- a/src/main/java/com/garbagemule/MobArena/util/Enums.java +++ b/src/main/java/com/garbagemule/MobArena/util/Enums.java @@ -2,6 +2,14 @@ package com.garbagemule.MobArena.util; public class Enums { + public static > E valueOf(Class type, String value) { + try { + return Enum.valueOf(type, value); + } catch (IllegalArgumentException e) { + return null; + } + } + /** * Get the enum value of a string, null if it doesn't exist. */ diff --git a/src/test/java/com/garbagemule/MobArena/housekeeper/BlockCleanerTest.java b/src/test/java/com/garbagemule/MobArena/housekeeper/BlockCleanerTest.java new file mode 100644 index 0000000..86dbaba --- /dev/null +++ b/src/test/java/com/garbagemule/MobArena/housekeeper/BlockCleanerTest.java @@ -0,0 +1,36 @@ +package com.garbagemule.MobArena.housekeeper; + +import com.garbagemule.MobArena.framework.Arena; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class BlockCleanerTest { + + @Test + public void defaultCleanerSetsArenaBlocksToAir() { + Block block1 = mock(Block.class); + Block block2 = mock(Block.class); + Block block3 = mock(Block.class); + Set blocks = new HashSet<>(Arrays.asList(block1, block2, block3)); + Arena arena = mock(Arena.class); + when(arena.getBlocks()).thenReturn(blocks); + BlockCleaner subject = BlockCleaner.getDefault(); + + subject.clean(arena); + + verify(block1).setType(Material.AIR); + verify(block2).setType(Material.AIR); + verify(block3).setType(Material.AIR); + } + +} diff --git a/src/test/java/com/garbagemule/MobArena/housekeeper/EntityCleanerTest.java b/src/test/java/com/garbagemule/MobArena/housekeeper/EntityCleanerTest.java new file mode 100644 index 0000000..1e9a169 --- /dev/null +++ b/src/test/java/com/garbagemule/MobArena/housekeeper/EntityCleanerTest.java @@ -0,0 +1,102 @@ +package com.garbagemule.MobArena.housekeeper; + +import com.garbagemule.MobArena.framework.Arena; +import com.garbagemule.MobArena.region.ArenaRegion; +import org.bukkit.Chunk; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; + +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class EntityCleanerTest { + + @Test + public void defaultCleanerRemovesDefaultsOnly() { + Entity[] defaults = new Entity[] { + fake(EntityType.ARROW), + fake(EntityType.BOAT), + fake(EntityType.DROPPED_ITEM), + fake(EntityType.EXPERIENCE_ORB), + fake(EntityType.MINECART), + fake(EntityType.SHULKER_BULLET), + }; + Entity[] extras = new Entity[] { + fake(EntityType.ARMOR_STAND), + fake(EntityType.PIG), + }; + Arena arena = mock(Arena.class); + ArenaRegion region = mock(ArenaRegion.class); + Chunk chunk = mock(Chunk.class); + when(arena.getRegion()).thenReturn(region); + when(region.getChunks()).thenReturn(Collections.singletonList(chunk)); + when(chunk.getEntities()).thenReturn(concat(defaults, extras)); + EntityCleaner subject = EntityCleaner.getDefault(); + + subject.clean(arena); + + for (Entity entity : defaults) { + verify(entity).remove(); + } + for (Entity entity : extras) { + verify(entity, never()).remove(); + } + } + + @Test + public void customCleanerRemovesProvidedTypesOnly() { + Entity[] removed = new Entity[] { + fake(EntityType.DROPPED_ITEM), + fake(EntityType.DROPPED_ITEM), + fake(EntityType.ARMOR_STAND), + fake(EntityType.DROPPED_ITEM), + }; + Entity[] retained = new Entity[] { + fake(EntityType.MINECART), + fake(EntityType.EXPERIENCE_ORB), + fake(EntityType.EXPERIENCE_ORB), + fake(EntityType.EXPERIENCE_ORB), + fake(EntityType.ARROW), + fake(EntityType.ARROW), + }; + Arena arena = mock(Arena.class); + ArenaRegion region = mock(ArenaRegion.class); + Chunk chunk = mock(Chunk.class); + when(arena.getRegion()).thenReturn(region); + when(region.getChunks()).thenReturn(Collections.singletonList(chunk)); + when(chunk.getEntities()).thenReturn(concat(removed, retained)); + EntityCleaner subject = new EntityCleaner(EnumSet.of( + EntityType.DROPPED_ITEM, + EntityType.ARMOR_STAND + )); + + subject.clean(arena); + + for (Entity entity : removed) { + verify(entity).remove(); + } + for (Entity entity : retained) { + verify(entity, never()).remove(); + } + } + + private static Entity fake(EntityType type) { + Entity entity = mock(Entity.class); + when(entity.getType()).thenReturn(type); + return entity; + } + + private static Entity[] concat(Entity[] a, Entity[] b) { + Entity[] result = Arrays.copyOf(a, a.length + b.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } + +} diff --git a/src/test/java/com/garbagemule/MobArena/housekeeper/MonsterCleanerTest.java b/src/test/java/com/garbagemule/MobArena/housekeeper/MonsterCleanerTest.java new file mode 100644 index 0000000..b67c5a8 --- /dev/null +++ b/src/test/java/com/garbagemule/MobArena/housekeeper/MonsterCleanerTest.java @@ -0,0 +1,26 @@ +package com.garbagemule.MobArena.housekeeper; + +import com.garbagemule.MobArena.MonsterManager; +import com.garbagemule.MobArena.framework.Arena; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.StrictStubs.class) +public class MonsterCleanerTest { + + @Test + public void defaultCleanerClearsMonsterManager() { + Arena arena = mock(Arena.class); + MonsterManager monsters = mock(MonsterManager.class); + when(arena.getMonsterManager()).thenReturn(monsters); + MonsterCleaner subject = MonsterCleaner.getDefault(); + + subject.clean(arena); + + verify(monsters).clear(); + } + +}