diff --git a/src/main/java/world/bentobox/aoneblock/AOneBlock.java b/src/main/java/world/bentobox/aoneblock/AOneBlock.java index 44acf90..ecde518 100644 --- a/src/main/java/world/bentobox/aoneblock/AOneBlock.java +++ b/src/main/java/world/bentobox/aoneblock/AOneBlock.java @@ -38,6 +38,7 @@ import world.bentobox.bentobox.api.flags.Flag.Mode; import world.bentobox.bentobox.api.flags.Flag.Type; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.RanksManager; /** * Main OneBlock class - provides an island minigame in the sky @@ -46,29 +47,57 @@ */ public class AOneBlock extends GameModeAddon { + /** Suffix for the nether world */ private static final String NETHER = "_nether"; + /** Suffix for the end world */ private static final String THE_END = "_the_end"; + /** Whether ItemsAdder is present on the server */ private boolean hasItemsAdder = false; - // Settings + /** The addon settings */ private Settings settings; + /** The custom chunk generator for OneBlock worlds */ private ChunkGeneratorWorld chunkGenerator; + /** The configuration object for settings */ private final Config configObject = new Config<>(this, Settings.class); + /** The listener for block-related events */ private BlockListener blockListener; + /** The manager for OneBlock phases and blocks */ private OneBlocksManager oneBlockManager; + /** The placeholder manager for AOneBlock */ private AOneBlockPlaceholders phManager; + /** The listener for hologram-related events */ private HoloListener holoListener; - // Flag + /** + * Flag to enable or disable start safety for players. + */ public final Flag START_SAFETY = new Flag.Builder("START_SAFETY", Material.BAMBOO_BLOCK) .mode(Mode.BASIC) .type(Type.WORLD_SETTING) .listener(new StartSafetyListener(this)) .defaultSetting(false) .build(); + /** The listener for the boss bar */ private BossBarListener bossBar = new BossBarListener(this); - public final Flag ONEBLOCK_BOSSBAR = new Flag.Builder("ONEBLOCK_BOSSBAR", Material.DRAGON_HEAD).mode(Mode.BASIC) - .type(Type.SETTING).listener(bossBar).defaultSetting(true).build(); + /** + * Flag to enable or disable the OneBlock boss bar. + */ + public final Flag ONEBLOCK_BOSSBAR = new Flag.Builder("ONEBLOCK_BOSSBAR", Material.DRAGON_HEAD) + .mode(Mode.BASIC) + .type(Type.SETTING) + .listener(bossBar) + .defaultSetting(true) + .build(); + + /** + * Flag to set who can break the magic block. + */ + public final Flag MAGIC_BLOCK = new Flag.Builder("MAGIC_BLOCK", Material.GRASS_BLOCK) + .mode(Mode.BASIC) + .type(Type.PROTECTION) + .defaultRank(RanksManager.COOP_RANK) + .build(); @Override public void onLoad() { @@ -94,9 +123,15 @@ public void onLoad() { getPlugin().getFlagsManager().registerFlag(this, START_SAFETY); // Bossbar getPlugin().getFlagsManager().registerFlag(this, this.ONEBLOCK_BOSSBAR); + // Magic Block protection + getPlugin().getFlagsManager().registerFlag(this, this.MAGIC_BLOCK); } } + /** + * Loads the settings from the config file. + * @return true if settings were loaded successfully, false otherwise. + */ private boolean loadSettings() { // Load settings again to get worlds settings = configObject.loadConfigObject(); @@ -114,11 +149,14 @@ private boolean loadSettings() { @Override public void onEnable() { + // Initialize the OneBlock manager oneBlockManager = new OneBlocksManager(this); + // Load phase data if (loadData()) { // Failed to load - don't register anything return; } + // Initialize and register listeners blockListener = new BlockListener(this); registerListener(blockListener); registerListener(new NoBlockHandler(this)); @@ -138,12 +176,15 @@ public void onEnable() { registerListener(holoListener); } - // Load phase data + /** + * Load phase data from oneblock.yml. + * @return true if there was an error, false otherwise. + */ public boolean loadData() { try { oneBlockManager.loadPhases(); } catch (IOException e) { - // Disable + // Disable the addon if phase data cannot be loaded logError("AOneBlock settings could not load (oneblock.yml error)! Addon disabled."); logError(e.getMessage()); setState(State.DISABLED); @@ -169,6 +210,7 @@ public void onDisable() { public void onReload() { // save cache blockListener.saveCache(); + // Reload settings and phase data if (loadSettings()) { log("Reloaded AOneBlock settings"); loadData(); @@ -223,6 +265,7 @@ private World getWorld(String worldName2, Environment env, ChunkGeneratorWorld c worldName2 = env.equals(World.Environment.NETHER) ? worldName2 + NETHER : worldName2; worldName2 = env.equals(World.Environment.THE_END) ? worldName2 + THE_END : worldName2; WorldCreator wc = WorldCreator.name(worldName2).environment(env); + // Use custom generator if configured, otherwise default World w = settings.isUseOwnGenerator() ? wc.createWorld() : wc.generator(chunkGenerator2).createWorld(); // Set spawn rates if (w != null) { @@ -232,6 +275,10 @@ private World getWorld(String worldName2, Environment env, ChunkGeneratorWorld c } + /** + * Sets the spawn rates for a given world based on the addon's settings. + * @param w The world to set spawn rates for. + */ private void setSpawnRates(World w) { if (getSettings().getSpawnLimitMonsters() > 0) { w.setSpawnLimit(SpawnCategory.MONSTER, getSettings().getSpawnLimitMonsters()); @@ -298,6 +345,9 @@ public OneBlockIslands getOneBlocksIsland(@NonNull Island i) { return blockListener.getIsland(Objects.requireNonNull(i)); } + /** + * @return The OneBlock manager. + */ public OneBlocksManager getOneBlockManager() { return oneBlockManager; } @@ -341,6 +391,10 @@ public void setIslandWorld(World world) { } + /** + * Sets the addon's settings. Used only for testing. + * @param settings The settings to set. + */ public void setSettings(Settings settings) { this.settings = settings; } diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java index 25fcd0a..d7f5381 100644 --- a/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java +++ b/src/main/java/world/bentobox/aoneblock/listeners/BlockListener.java @@ -27,6 +27,7 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -56,14 +57,16 @@ import world.bentobox.bentobox.api.events.island.IslandCreatedEvent; import world.bentobox.bentobox.api.events.island.IslandDeleteEvent; import world.bentobox.bentobox.api.events.island.IslandResettedEvent; +import world.bentobox.bentobox.api.flags.FlagListener; import world.bentobox.bentobox.database.Database; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.util.Util; /** + * This listener handles all the core logic for the OneBlock block, including breaking, phase changes, and entity spawning. * @author tastybento */ -public class BlockListener implements Listener { +public class BlockListener extends FlagListener implements Listener { /** * Main addon class. @@ -76,36 +79,36 @@ public class BlockListener implements Listener { private final OneBlocksManager oneBlocksManager; /** - * Oneblock data database + * Oneblock data database handler. */ private final Database handler; /** - * Oneblock cache. + * In-memory cache for OneBlock island data to reduce database lookups. */ private final Map cache; /** - * Phase checker class + * Helper class to check phase requirements. */ private final CheckPhase check; /** - * Sound player + * Helper class to play warning sounds for upcoming mobs. */ private final WarningSounder warningSounder; /** - * How many blocks ahead it should look. + * How many blocks ahead the queue should look when populating. */ public static final int MAX_LOOK_AHEAD = 5; /** - * How often data is saved. + * How often island data is saved to the database (in blocks broken). */ public static final int SAVE_EVERY = 50; - - // Loot for suspicious blocks + + /** Loot table for suspicious blocks. Maps item to its probability. */ private static final Map LOOT; static { Map loot = new HashMap<>(); @@ -142,7 +145,8 @@ public class BlockListener implements Listener { } /** - * @param addon - OneBlock + * Constructs the BlockListener. + * @param addon - The AOneBlock addon instance. */ public BlockListener(@NonNull AOneBlock addon) { this.addon = addon; @@ -154,7 +158,7 @@ public BlockListener(@NonNull AOneBlock addon) { } /** - * Save the island cache + * Saves all island data from the cache to the database asynchronously. */ public void saveCache() { cache.values().forEach(handler::saveObjectAsync); @@ -164,6 +168,10 @@ public void saveCache() { // Section: Listeners // --------------------------------------------------------------------- + /** + * Sets up a new OneBlock island when a BentoBox island is created. + * @param e The IslandCreatedEvent. + */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIsland(IslandCreatedEvent e) { if (addon.inWorld(e.getIsland().getWorld())) { @@ -171,6 +179,10 @@ public void onNewIsland(IslandCreatedEvent e) { } } + /** + * Resets a OneBlock island when a BentoBox island is reset. + * @param e The IslandResettedEvent. + */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onNewIsland(IslandResettedEvent e) { if (addon.inWorld(e.getIsland().getWorld())) { @@ -178,6 +190,10 @@ public void onNewIsland(IslandResettedEvent e) { } } + /** + * Removes OneBlock data when a BentoBox island is deleted. + * @param e The IslandDeleteEvent. + */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onDeletedIsland(IslandDeleteEvent e) { if (addon.inWorld(e.getIsland().getWorld())) { @@ -201,6 +217,10 @@ public void onBlockFromTo(final BlockFromToEvent e) { e.setCancelled(addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())).isPresent()); } + /** + * Handles the breaking of the magic block by a player. + * @param e The BlockBreakEvent. + */ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onBlockBreak(final BlockBreakEvent e) { if (!addon.inWorld(e.getBlock().getWorld())) { @@ -303,6 +323,10 @@ public void onItemSpawn(ItemSpawnEvent event) { // Section: Processing methods // --------------------------------------------------------------------- + /** + * Sets up the initial state for a new OneBlock island. + * @param island The island to set up. + */ private void setUp(@NonNull Island island) { // Set the bedrock to the initial block Util.getChunkAtAsync(Objects.requireNonNull(island.getCenter())) @@ -315,20 +339,26 @@ private void setUp(@NonNull Island island) { } /** - * Main block processing method that handles the magic block mechanics. + * Main magic block processing method that handles the magic block mechanics. * This includes phase changes, block spawning, and event handling. * * @param e - event causing the processing - * @param i - island where it's happening + * @param island - island where it's happening * @param player - player who broke the block or who is involved - may be null * @param world - world where the block is being broken */ - private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player player, @NonNull World world) { - Block block = Objects.requireNonNull(i.getCenter()).toVector().toLocation(world).getBlock(); - OneBlockIslands is = getIsland(i); + private void process(@NonNull Cancellable e, @NonNull Island island, @Nullable Player player, @NonNull World world) { + // Check if the player has authority to break the magic block + if (!checkIsland((@NonNull Event) e, player, island.getCenter(), addon.MAGIC_BLOCK)) { + // Not allowed + return; + } + + Block block = Objects.requireNonNull(island.getCenter()).toVector().toLocation(world).getBlock(); + OneBlockIslands is = getIsland(island); // Process phase changes and requirements - ProcessPhaseResult phaseResult = processPhase(e, i, is, player, world, block); + ProcessPhaseResult phaseResult = processPhase(e, island, is, player, world, block); if (e.isCancelled()) { return; } @@ -337,12 +367,18 @@ private void process(@NonNull Cancellable e, @NonNull Island i, @Nullable Player initializeQueue(is, phaseResult.phase, phaseResult.isCurrPhaseNew); // Process hologram and warning sounds - processHologramAndWarnings(i, is, phaseResult.phase, block); + processHologramAndWarnings(island, is, phaseResult.phase, block); // Process the next block - processNextBlock(e, i, player, block, is, phaseResult); + processNextBlock(e, island, player, block, is, phaseResult); } + /** + * A record to hold the result of phase processing. + * @param phase The current phase. + * @param isCurrPhaseNew Whether the current phase is new. + * @param blockNumber The block number within the current phase. + */ private record ProcessPhaseResult(OneBlockPhase phase, boolean isCurrPhaseNew, int blockNumber) {} /** @@ -370,12 +406,14 @@ private ProcessPhaseResult processPhase(Cancellable e, Island i, OneBlockIslands boolean isCurrPhaseNew = !is.getPhaseName().equalsIgnoreCase(currPhaseName); if (isCurrPhaseNew) { + // Check if the player meets the requirements for the new phase. if (check.phaseRequirementsFail(player, i, is, phase, world)) { e.setCancelled(true); return new ProcessPhaseResult(phase, true, 0); } handleNewPhase(player, i, is, phase, block, prevPhaseName); } else if (is.getBlockNumber() % SAVE_EVERY == 0) { + // Periodically save the island's progress. saveIsland(i); } @@ -498,7 +536,7 @@ private void handleEntitySpawn(Cancellable e, Island i, Player player, Block blo } /** - * Handles different types of block breaking events. + * Handles different types of block breaking events (player, bucket, entity). * * @param e - event being processed * @param i - island instance @@ -517,7 +555,7 @@ private void handleBlockBreak(Cancellable e, Island i, Player player, Block bloc } /** - * Handles bucket fill events including block spawning and event firing. + * Handles bucket fill events on the magic block. * * @param player - player filling bucket * @param i - island instance @@ -560,6 +598,11 @@ private OneBlockPhase handleGoto(OneBlockIslands is, int gotoBlock) { return oneBlocksManager.getPhase(gotoBlock); } + /** + * Sets the biome in a small radius around the given block. + * @param block The center block. + * @param biome The biome to set. + */ private void setBiome(@NonNull Block block, @Nullable Biome biome) { if (biome == null) { return; @@ -603,6 +646,11 @@ private void breakBlock(@Nullable Player player, Block block, @NonNull OneBlockO .callEvent(new MagicBlockEvent(island, player.getUniqueId(), tool, block, nextBlock.getMaterial())); } + /** + * Spawns the next block in the sequence, handling custom blocks and block data. + * @param nextBlock The object representing the block to spawn. + * @param block The block in the world to be replaced. + */ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block) { if (nextBlock.isCustomBlock()) { nextBlock.getCustomBlock().execute(addon, block); @@ -624,6 +672,11 @@ private void spawnBlock(@NonNull OneBlockObject nextBlock, @NonNull Block block) } + /** + * Handles player interaction with suspicious blocks (sand/gravel) using a brush. + * This is currently for debugging purposes. + * @param e The PlayerInteractEvent. + */ @EventHandler public void onPlayerInteract(PlayerInteractEvent e) { if (e.getAction() != Action.RIGHT_CLICK_BLOCK) return; @@ -632,12 +685,18 @@ public void onPlayerInteract(PlayerInteractEvent e) { if (e.getClickedBlock().getType() != Material.SUSPICIOUS_GRAVEL && e.getClickedBlock().getType() != Material.SUSPICIOUS_SAND) return; if (e.getPlayer().getInventory().getItemInMainHand().getType() != Material.BRUSH) return; + // TODO FINISH THIS!!! BentoBox.getInstance().logDebug("Brushing " + e.getClickedBlock()); if (e.getClickedBlock() != null && e.getClickedBlock().getBlockData() instanceof Brushable bb) { BentoBox.getInstance().logDebug("item is brushable " + bb.getDusted()); } } + /** + * Gets a random loot item from the loot table based on probabilities. + * @param random The random number generator. + * @return A random Material from the loot table. + */ private static Material getRandomLoot(Random random) { double roll = random.nextDouble(); double cumulative = 0.0; @@ -654,6 +713,11 @@ private static Material getRandomLoot(Random random) { return materials.get(random.nextInt(materials.size())); } + /** + * Spawns an entity at the magic block location. + * @param nextBlock The object containing entity information. + * @param block The magic block. + */ private void spawnEntity(@NonNull OneBlockObject nextBlock, @NonNull Block block) { if (block.isEmpty()) block.setType(Material.STONE); @@ -666,6 +730,11 @@ private void spawnEntity(@NonNull OneBlockObject nextBlock, @NonNull Block block block.getWorld().playSound(block.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1F, 2F); } + /** + * Fills a chest with items and adds particle effects based on rarity. + * @param nextBlock The object containing chest information. + * @param block The chest block. + */ private void fillChest(@NonNull OneBlockObject nextBlock, @NonNull Block block) { Chest chest = (Chest) block.getState(); nextBlock.getChest().forEach(chest.getBlockInventory()::setItem); @@ -708,6 +777,12 @@ public List getAllIslands() { return handler.loadObjects(); } + /** + * Loads an island's OneBlock data from the database into the cache. + * If it doesn't exist, a new object is created. + * @param uniqueId The unique ID of the island. + * @return The OneBlockIslands data object. + */ @NonNull private OneBlockIslands loadIsland(@NonNull String uniqueId) { if (handler.objectExists(uniqueId)) { diff --git a/src/main/java/world/bentobox/aoneblock/listeners/BlockProtect.java b/src/main/java/world/bentobox/aoneblock/listeners/BlockProtect.java index 2f9ac39..70babd9 100644 --- a/src/main/java/world/bentobox/aoneblock/listeners/BlockProtect.java +++ b/src/main/java/world/bentobox/aoneblock/listeners/BlockProtect.java @@ -26,12 +26,20 @@ import world.bentobox.aoneblock.AOneBlock; import world.bentobox.bentobox.database.objects.Island; +/** + * This listener class provides protection for the OneBlock, + * preventing it from being destroyed or moved by various means. + */ public class BlockProtect implements Listener { + /** The color green for particles. */ public static final Color GREEN = Color.fromBGR(0, 100, 0); + /** The list of particles to be used for sparkles. */ private static final List PARTICLES = new ArrayList<>(List.of(Particle.DUST)); + /** An iterator for the particle list to cycle through them. */ private Iterator particleIterator = Collections.emptyIterator(); + /** The AOneBlock addon instance. */ private final AOneBlock addon; /** @@ -50,29 +58,39 @@ public void onBlockDamage(PlayerInteractEvent e) { Action action = e.getAction(); String clickType = addon.getSettings().getClickType(); + // Exit if click type is NONE, player is not in a OneBlock world, or no block was clicked. if (clickType.equalsIgnoreCase("NONE") || !addon.inWorld(e.getPlayer().getWorld()) || e.getClickedBlock() == null) { return; } + // Check if the action matches the configured click type. if ((action == Action.LEFT_CLICK_BLOCK && clickType.equalsIgnoreCase("LEFT")) || (action == Action.RIGHT_CLICK_BLOCK && clickType.equalsIgnoreCase("RIGHT"))) { Location l = e.getClickedBlock().getLocation(); + // If the clicked block is a OneBlock, show sparkles. addon.getIslands().getIslandAt(l).map(Island::getCenter).filter(center -> center.equals(l)) .ifPresent(this::showSparkles); } } + /** + * Spawns particles around a given location to create a sparkle effect. + * @param location The location to spawn particles at. + */ public void showSparkles(Location location) { + // Reset the particle iterator if it has been exhausted. if (!particleIterator.hasNext()) { Collections.shuffle(PARTICLES); particleIterator = PARTICLES.iterator(); } Particle p = particleIterator.next(); + // Iterate over a 2x1.5x2 box around the block to spawn particles. for (double x = -0.5; x <= 1.5; x += addon.getSettings().getParticleDensity()) { for (double y = 0.0; y <= 1.5; y += addon.getSettings().getParticleDensity()) { for (double z = -0.5; z < 1.5; z += addon.getSettings().getParticleDensity()) { + // Spawn a dust particle with the configured color and size. location.getWorld().spawnParticle(p, location.clone().add(new Vector(x, y, z)), 5, 0.1, 0, 0.1, 1, new Particle.DustOptions(addon.getSettings().getParticleColor(), addon.getSettings().getParticleSize().floatValue())); @@ -92,6 +110,7 @@ public void onBlockChange(final EntityChangeBlockEvent e) { return; } Location l = e.getBlock().getLocation(); + // If the block being changed is a OneBlock, cancel the event. addon.getIslands().getIslandAt(l).filter(i -> l.equals(i.getCenter())).ifPresent(i -> e.setCancelled(true)); } @@ -104,6 +123,7 @@ public void onExplosion(final EntityExplodeEvent e) { if (!addon.inWorld(e.getLocation().getWorld())) { return; } + // Remove any OneBlock from the list of blocks to be exploded. e.blockList().removeIf(b -> addon.getIslands().getIslandAt(b.getLocation()).filter(i -> b.getLocation().equals(i.getCenter())).isPresent()); } @@ -123,6 +143,13 @@ public void onPistonExtend(BlockPistonExtendEvent e) { public void onPistonRetract(BlockPistonRetractEvent e) { checkPiston(e, e.getBlock(), e.getBlocks()); } + + /** + * Checks if a piston action (extend or retract) would move a OneBlock and cancels it if so. + * @param e The cancellable piston event. + * @param block The piston block. + * @param blocks The list of blocks that would be moved. + */ private void checkPiston(Cancellable e, Block block, List blocks) { if (!addon.inWorld(block.getWorld())) { return; @@ -144,7 +171,8 @@ public void onFallingBlockSpawn(EntitySpawnEvent e) { return; } Location l = e.getLocation(); - // Dropped blocks do not spawn on integer locations, so we have to check block values independently + // Dropped blocks do not spawn on integer locations, so we have to check block values independently. + // If the falling block is at the location of a OneBlock, cancel the spawn. addon.getIslands().getIslandAt(l).filter(i -> l.getBlockX() == i.getCenter().getBlockX() && l.getBlockY() == i.getCenter().getBlockY() && l.getBlockZ() == i.getCenter().getBlockZ() diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 3345596..24ee757 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -5,7 +5,13 @@ protection: flags: - START_SAFETY: + MAGIC_BLOCK: + name: Magic Block Protection + description: | + &b Rank that can break the magic + &b block if they can break blocks. + hint: &c Your rank cannot break the magic block! + START_SAFETY: name: Starting Safety description: | &b Prevents new players