diff --git a/pom.xml b/pom.xml index d2832c7..70b1b99 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ nu.nerd DragonFight - 1.1.3 + 1.1.4 Custom dragon fight. https://github.com/NerdNu/${project.name} diff --git a/src/main/java/nu/nerd/df/FightState.java b/src/main/java/nu/nerd/df/FightState.java index a9cb038..70808d8 100644 --- a/src/main/java/nu/nerd/df/FightState.java +++ b/src/main/java/nu/nerd/df/FightState.java @@ -75,7 +75,7 @@ // ---------------------------------------------------------------------------- /** * Records state information about the current dragon fight. - * + * * Define the term "arena" to mean the space within the circle of obsidian * pillars in the end. */ @@ -118,7 +118,7 @@ public void onDisable() { // ------------------------------------------------------------------------ /** * Implement the /dragon info command. - * + * * This command is for ordinary players to check the fight status. */ public void cmdPlayerInfo(CommandSender sender) { @@ -137,10 +137,10 @@ public void cmdPlayerInfo(CommandSender sender) { // ------------------------------------------------------------------------ /** * Implement the /df info command. - * + * * Show information about the current fight: stage, owner, boss health and * dragon health. - * + * * @param sender the command sender, for message sending. */ public void cmdInfo(CommandSender sender) { @@ -199,9 +199,9 @@ public void cmdInfo(CommandSender sender) { // ------------------------------------------------------------------------ /** * Implement the /df stop command. - * + * * Stop the current fight and clean up mobs and projectiles. - * + * * @param sender the command sender, for message sending. */ public void cmdStop(CommandSender sender) { @@ -248,10 +248,10 @@ public void cmdStop(CommandSender sender) { // ------------------------------------------------------------------------ /** * Implement the /df next command. - * + * * Remove the current stage boss and fight-associated support mobs and * projectiles, and skip to the next stage. - * + * * @param sender the command sender, for message sending. */ public void cmdNextStage(CommandSender sender) { @@ -278,16 +278,16 @@ public void cmdNextStage(CommandSender sender) { // ------------------------------------------------------------------------ /** * Implement the /df stage command. - * + * * Remove the current stage boss and fight-associated support mobs and * projectiles, and start the specified stage. - * + * * The current implementation only supports skipping forwards - not going * backwards. - * - * @param sender the command sender, for message sending. + * + * @param sender the command sender, for message sending. * @param stageNumber the new stage number from 0 (stopped) to 11 (dragon - * only). + * only). */ public void cmdSkipToStage(CommandSender sender, int stageNumber) { if (stageNumber < 0 || stageNumber > 11) { @@ -330,7 +330,7 @@ public void cmdSkipToStage(CommandSender sender, int stageNumber) { // ------------------------------------------------------------------------ /** * Implement the /dragon prize command. - * + * * @param sender the command sender, for message sending. */ public void cmdDragonPrize(CommandSender sender) { @@ -364,7 +364,7 @@ public void cmdDragonPrize(CommandSender sender) { // ------------------------------------------------------------------------ /** * Log messages. - * + * * @param message the message. */ public static void log(String message) { @@ -375,7 +375,7 @@ public static void log(String message) { // ------------------------------------------------------------------------ /** * Log debug messages. - * + * * @param message the message. */ public static void debug(String message) { @@ -417,9 +417,9 @@ protected void cleanUp(CommandSender sender) { /** * When the plugin initialises, infer the state of the fight, including the * stage number, based on the presence of crystals, the dragon and bosses. - * + * * Find the ender crystals that are part of the current dragon fight. - * + * * There is no ChunkLoadEvent for spawn chunks, so we need to scan loaded * chunks on startup. */ @@ -672,9 +672,9 @@ protected void onPlayerLogin(PlayerLoginEvent event) { /** * When the dragon spawns, handle it with * {@link #onDragonSpawn(EnderDragon)}. - * + * * Track any mobs that spawn with the boss group. - * + * * Handle end crystals spawns during the pillar-summoning phase only with * {@link #onPillarCrystalSpawn(EnderCrystal)}. */ @@ -707,7 +707,7 @@ protected void onEntitySpawn(EntitySpawnEvent event) { // Make the summoning crystals glowing and invulnerable to prevent // the player from initiating the fight and then destroying the // spawning crystals. - if (isDragonSpawnCrystalLocation(loc)) { + if (isDragonSpawnCrystalLocation(loc.getBlock().getRelative(0, -1, 0).getLocation())) { entity.setInvulnerable(true); entity.setGlowing(true); entity.getScoreboardTags().add(SPAWNING_CRYSTAL_TAG); @@ -759,7 +759,7 @@ protected void onChunkUnload(ChunkUnloadEvent event) { /** * When a projectile is launched by a mob with the ENTITY_TAG tag or group, * tag it with ENTITY_TAG so we can easily clean it up later. - * + * * The dragon is tagged thus so that its projectiles can also be removed. */ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) @@ -782,7 +782,7 @@ protected void onProjectileLaunch(ProjectileLaunchEvent event) { /** * Protect the End Crystals on the pillars and the four crystals that spawn * the dragon. - * + * * I had some problems with setInvulnerable(); it doesn't just work * on the 10 pillar crystals. If you think you can just set them * invulnerable when they spawn well no, that would be way too simple. What @@ -793,7 +793,7 @@ protected void onProjectileLaunch(ProjectileLaunchEvent event) { * the crystals will be protected. So this crystal protection code is here * partly for curiosity and partly to fend off any race condition in the * transition. - * + * * This event needs to be processed before e.g. SafeCrystals drops a * crystal. */ @@ -822,7 +822,7 @@ public void onEntityDamageEarly(EntityDamageEvent event) { /** * What is this madness? Two handlers for the same event?! Well, one of them * has to be early and the other late. - * + * * We need to check the final outcome of the EntityDamageEvent in order to * correctly update boss state, since some other plugin may have cancelled * the event. @@ -852,7 +852,7 @@ public void onEntityDamageLate(EntityDamageEvent event) { // ------------------------------------------------------------------------ /** * Prevent fight-relevant crystals from exploding. - * + * * These will be the pillar crystals, or the dragon spawning ones. We can't * rely on checking for bedrock under them because of pistons. * @@ -877,7 +877,7 @@ public void onEntityExplode(EntityExplodeEvent event) { // ------------------------------------------------------------------------ /** * Prevent pillar crystals from being set on fire. - * + * * This is easier said than done. You can cancel the event, or set the fire * ticks after the fact to be small so that the fire extinguishes itself * quickly, or both. Either way, the current 1.15.2 client just shows the @@ -957,12 +957,12 @@ protected void onEntityPortal(EntityPortalEvent event) { /** * Prevent players from placing end crystals on bedrock in the end during * the SUMMONING_PILLARS phase of the dragon fight. - * + * * This is a simple precaution to ensure that any crystals spawning during * that phase are because of the dragon fight, and not due to players. * EntitySpawnEvent doesn't give us a spawn reason that would disambiguate * the two. - * + * * Also, prevent players from placing crystals in arbitrary locations on the * end portal frame bedrock. Crystals can only be placed in the four * designated "dragon spawning crystal" locations. The player gets a message @@ -1036,7 +1036,7 @@ protected void onEnderDragonChangePhase(EnderDragonChangePhaseEvent event) { /** * When an end crystal spawns at the start of the fight, play accompanying * effects and register the entity. - * + * * Tag crystals that were spawned by the dragon fight to prevent us from * mis-identifying player-placed crystals as relevant. */ @@ -1061,11 +1061,11 @@ protected void onPillarCrystalSpawn(EnderCrystal crystal) { // ------------------------------------------------------------------------ /** * When the dragon spawns, schedule the start of phase 1. - * + * * We need to be careful about badly-timed restarts (as always). Since we * detect phases on reload by the absence of crystals, remove the crystal * before adding the boss. - * + * * Experimentation reveals that at the time the dragon spawns, the spawning * crystals still exist and the RespawnPhase is NONE. */ @@ -1118,12 +1118,12 @@ protected void onDragonSpawn(EnderDragon dragon) { // ------------------------------------------------------------------------ /** * When the dragon dies: - * + * * - * + * * @param dragon the dragon that died. */ protected void onDragonDeath(EnderDragon dragon) { @@ -1179,7 +1179,7 @@ protected void onDragonDeath(EnderDragon dragon) { /** * Give all of the specified dragon death prizes to the player, or none of * them if not all can fit in the player's inventory. - * + * * @param player the player. * @param prizes the list of items to give. * @return true if all prizes could fit; false if they were deferred. @@ -1192,7 +1192,7 @@ protected static boolean givePrizes(Player player, List prizes) { // Check that the player has enough open inventory slots. int openSlots = (int) Stream.of(player.getInventory().getStorageContents()) - .filter(i -> (i == null || i.getType() == Material.AIR)).count(); + .filter(i -> (i == null || i.getType() == Material.AIR)).count(); log(player.getName() + " has " + openSlots + " empty slots for " + prizes.size() + " items."); if (openSlots < prizes.size()) { @@ -1220,7 +1220,7 @@ protected static boolean givePrizes(Player player, List prizes) { // ------------------------------------------------------------------------ /** * Generate all ItemStack for a single dragon prize. - * + * * @return a list of ItemStacks. */ protected static List generatePrizes() { @@ -1247,21 +1247,23 @@ protected static List generatePrizes() { // ------------------------------------------------------------------------ /** - * Return true if the specified location is somewhere you would place an end - * crystal to spawn the dragon. - * + * Return true if the specified location is that of a bedrock block where + * you would place an end crystal to spawn the dragon. + * * I'm not really sure how low the end portal frame can occur. In the * current world, the frame is at Y56. This method doesn't care about the Y * coordinate, as long as its clearly not bedrock down below Y6 (which only * occurs in custom generated terrain). - * - * @param loc the location of an end crystal that has just spawned on - * obsidian. + * + * The method checks for bedrock (which cannot be placed by an ordinary + * player) under the crystal and checks for the right X and Z coordinates + * + * @param loc the location of the block under an end crystal. * @return true if the specified location is somewhere you would place an * end crystal to spawn the dragon. */ protected static boolean isDragonSpawnCrystalLocation(Location loc) { - if (loc.getY() < 51) { + if (loc.getY() < 6 || loc.getBlock().getType() != Material.BEDROCK) { return false; } @@ -1273,18 +1275,18 @@ protected static boolean isDragonSpawnCrystalLocation(Location loc) { // ------------------------------------------------------------------------ /** * Return all (0-4) dragon-spawning crystals currently in existence. - * + * * The range on the Y coordinate is largeish because I'm not sure of the * limits on the end portal spawn location. - * + * * @return all dragon-spawning crystals currently in existence. */ protected static List getDragonSpawnCrystals() { World fightWorld = DragonUtil.getFightWorld(); - return fightWorld.getNearbyEntities(new Location(fightWorld, 0, 57, 0), 3, 5, 3) - .stream().filter(e -> e.getType() == EntityType.ENDER_CRYSTAL && - isDragonSpawnCrystalLocation(e.getLocation())) - .collect(Collectors.toList()); + return fightWorld.getNearbyEntities(new Location(fightWorld, 0, 45, 0), 3, 40, 3) + .stream().filter(e -> e.getType() == EntityType.ENDER_CRYSTAL && + isDragonSpawnCrystalLocation(e.getLocation().getBlock().getRelative(0, -1, 0).getLocation())) + .collect(Collectors.toList()); } // ------------------------------------------------------------------------ @@ -1303,7 +1305,7 @@ protected void reconfigureDragonBossBar() { // ------------------------------------------------------------------------ /** * Start the next stage. - * + * * This method is called to do the boss spawning sequence for stages 1 to * 10. */ @@ -1353,10 +1355,10 @@ protected void nextStage() { // ------------------------------------------------------------------------ /** * Despawn the specified number of pillar crystals. - * + * * This method is used by the /df next and /df stage command * implementations. - * + * * @param count the number of crystals to untrack and despawn. */ protected void despawnPillarCrystals(int count) { @@ -1370,11 +1372,12 @@ protected void despawnPillarCrystals(int count) { // ------------------------------------------------------------------------ /** * Begin the specified boss stage (1 to 11 only). - * + * * Stage 0 is not supported. - * - * @param sender the command sender for messages, or null if unused. - * @param stageNumber the stage number from 1 to 10. + * + * @param sender the command sender for messages, or null if + * unused. + * @param stageNumber the stage number from 1 to 10. * @param bossSpawnLocation the location where the bosses are spawned. */ protected void startStage(CommandSender sender, int stageNumber, Location bossSpawnLocation) { @@ -1430,9 +1433,9 @@ protected void startStage11() { /** * Choose a random location to spawn the boss with a 3x3x3 volume of air * within the arena. - * + * * If a suitable location cannot be found, put it on the portal pillar. - * + * * If players decide to arrange the arena to frustrate efforts to find a * location to spawn the boss, moderate them hard. */ @@ -1470,7 +1473,7 @@ protected static Location getBossSpawnLocation() { /** * If the specified entity is a mob, teleport it to a boss spawn location, * playing particle and sound effects. - * + * * @param entity the mob. */ protected static void returnMobToBossSpawn(MobType mobType, Entity entity) { @@ -1482,8 +1485,8 @@ protected static void returnMobToBossSpawn(MobType mobType, Entity entity) { // ------------------------------------------------------------------------ /** * Play a sound effect at the location with standard options. - * - * @param loc the location. + * + * @param loc the location. * @param sound the sound. */ protected void playSound(Location loc, Sound sound) { @@ -1494,20 +1497,20 @@ protected void playSound(Location loc, Sound sound) { /** * Return the collection of players near the end portal that should see * titles. - * + * * @return nearby players. */ protected Set getNearbyPlayers() { World world = DragonUtil.getFightWorld(); return world.getPlayers().stream() - .filter(p -> DragonUtil.getMagnitude2D(p.getLocation()) < NEARBY_RADIUS) - .collect(Collectors.toSet()); + .filter(p -> DragonUtil.getMagnitude2D(p.getLocation()) < NEARBY_RADIUS) + .collect(Collectors.toSet()); } // ------------------------------------------------------------------------ /** * Update the BossBar for the current stage. - * + * * If we're not in stage 1-10, don't show a boss bar, even if we have some * test bosses. */ @@ -1544,7 +1547,7 @@ protected void updateBossBar() { // ------------------------------------------------------------------------ /** * Return the total health of all extant bosses. - * + * * @return the total health of all extant bosses. */ public double getTotalBossHealth() { @@ -1554,7 +1557,7 @@ public double getTotalBossHealth() { // ------------------------------------------------------------------------ /** * Vanilla code can randomly spawn extra dragons. - * + * * Try to adapt. Not clear on when the DragonBattle has its EnderDragon * reference set, so let's keep at least one, even if not referenced by the * battle. @@ -1574,8 +1577,8 @@ protected void removeSurplusDragons() { // Assume vanilla intends the newest dragon instance to replace // whatever others exist. Sort into ascending order by time existed. List dragonsByAge = dragons.stream() - .sorted((d1, d2) -> d1.getTicksLived() - d2.getTicksLived()) - .collect(Collectors.toCollection(ArrayList::new)); + .sorted((d1, d2) -> d1.getTicksLived() - d2.getTicksLived()) + .collect(Collectors.toCollection(ArrayList::new)); // Remove every dragon after the youngest. if (dragonsByAge.size() > 0) { @@ -1604,7 +1607,7 @@ protected void removeSurplusDragons() { // ------------------------------------------------------------------------ /** * A repeating task that tracks boss fight participants to: - * + * *
    *
  • Return bosses to the fight area when they go outside the designated * radius or below a certain Y coordinate.
  • @@ -1703,7 +1706,7 @@ public void run() { /** * Radius in blocks on the XZ plane within which players are considered to * be "nearby", i.e. close enough to see titles etc. - * + * * Note that only 2-D distance from the world origin is considered. */ private static final double NEARBY_RADIUS = 100.0; @@ -1737,7 +1740,7 @@ public void run() { /** * Radius in which entities are checked. - * + * * This needs to be larger than BOSS_RADIUS to account for the maximum * distance the mob could travel in PERIOD_TICKS. */ @@ -1763,7 +1766,7 @@ public void run() { /** * The current set of boss mobs. - * + * * Used to efficiently enforce movement limits and update the current * stage's boss bar. */ @@ -1771,9 +1774,9 @@ public void run() { /** * Current stage number. - * + * * in Stage N, N crystals have been removed. - * + * * Stage 0 is before the fight, Stage 1 => first crystal removed and boss * spawned. Stage 10 => final boss spawned. Stage 11: dragon. */