diff --git a/README.md b/README.md index 3c1e39c..239b97e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Quest Mob Spawns -This Spigot plugin will automatically spawn mobs required for every kill quest. The mobs will not harm or target any +This Spigot plugin will automatically spawn mobs required for every kill quest. The mobs will not harm or target any player unless the player has the kill quest, is on the stage for which the mobs should spawn, and has not yet killed the required amount of mobs. The spawned mobs will not drop any items or XP. The mobs are marked as spawner spawned mods, so other plugins, like mcMMO will not grant any skill XP from the quest mobs. diff --git a/pom.xml b/pom.xml index e27b98b..987d79d 100644 --- a/pom.xml +++ b/pom.xml @@ -82,5 +82,11 @@ 4.8.1 provided + + org.jetbrains + annotations + 24.0.1 + compile + diff --git a/src/main/java/net/knarcraft/questmobspawns/QuestMobSpawns.java b/src/main/java/net/knarcraft/questmobspawns/QuestMobSpawns.java index ac91dd6..f8538f0 100644 --- a/src/main/java/net/knarcraft/questmobspawns/QuestMobSpawns.java +++ b/src/main/java/net/knarcraft/questmobspawns/QuestMobSpawns.java @@ -18,6 +18,7 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; @@ -28,6 +29,9 @@ import java.util.Map; import java.util.Random; import java.util.Set; +/** + * This plugin's main class + */ public final class QuestMobSpawns extends JavaPlugin { private final Random random = new Random(); @@ -41,22 +45,20 @@ public final class QuestMobSpawns extends JavaPlugin { @Override public void onEnable() { plugin = this; - // Plugin startup logic + // Set up the quests API questsAPI = (QuestsAPI) Bukkit.getServer().getPluginManager().getPlugin("Quests"); if (questsAPI == null) { + this.setEnabled(false); return; } + + // Register listeners Bukkit.getPluginManager().registerEvents(new MobDeathListener(), this); Bukkit.getPluginManager().registerEvents(new MobBlockDamageListener(questsAPI), this); Bukkit.getScheduler().scheduleSyncRepeatingTask(this, this::spawnMobs, 200, 30 * 20); } - @Override - public void onDisable() { - // Plugin shutdown logic - } - /** * Spawns mobs for all kill areas as necessary */ @@ -109,7 +111,16 @@ public final class QuestMobSpawns extends JavaPlugin { } } - private void spawnMobsForStage(Map> questPlayerMap, IQuest quest, IStage stage, int index) { + /** + * Spawns necessary mobs for a given stage + * + * @param questPlayerMap

The set of quest-takers on the given stage

+ * @param quest

The quest to spawn mobs for

+ * @param stage

The stage to spawn mobs for

+ * @param index

The index of the kill location to spawn mobs for

+ */ + private void spawnMobsForStage(@NotNull Map> questPlayerMap, @NotNull IQuest quest, + @NotNull IStage stage, int index) { Location killLocation = stage.getLocationsToKillWithin().get(index).clone(); int radius = stage.getRadiiToKillWithin().get(index); @@ -140,14 +151,14 @@ public final class QuestMobSpawns extends JavaPlugin { int z = (int) Math.round((random.nextInt(radius * 2) - radius) + killLocation.getZ()); Block spawnBlock = killLocation.getWorld().getHighestBlockAt(x, z).getRelative(BlockFace.UP); Entity spawnedEntity = killLocation.getWorld().spawnEntity(spawnBlock.getLocation(), entityType); - + // Treat the spawned entity as the result of a spawner for plugins like mcMMO if (spawnedEntity instanceof LivingEntity) { CreatureSpawnEvent spawnEvent = new CreatureSpawnEvent((LivingEntity) spawnedEntity, CreatureSpawnEvent.SpawnReason.SPAWNER); Bukkit.getPluginManager().callEvent(spawnEvent); } - + QuestMobHelper.setSpawnedMob(spawnedEntity); QuestMobHelper.setQuestData(spawnedEntity, quest.getId(), quest.getStages().indexOf(stage), index, new int[]{killLocation.getBlockX(), killLocation.getBlockY(), killLocation.getBlockZ(), radius}); @@ -159,13 +170,13 @@ public final class QuestMobSpawns extends JavaPlugin { /** * Counts the number of entities of the necessary type already in the kill zone - * + * * @param killLocation

The location of the kill location's centre

- * @param radius

The radius of the kill zone

- * @param entityType

The type of entity to kill in the kill zone

+ * @param radius

The radius of the kill zone

+ * @param entityType

The type of entity to kill in the kill zone

* @return

*/ - private int entitiesInKillZone(Location killLocation, int radius, EntityType entityType) { + private int entitiesInKillZone(@NotNull Location killLocation, int radius, @NotNull EntityType entityType) { if (killLocation.getWorld() == null) { return 0; } @@ -195,8 +206,9 @@ public final class QuestMobSpawns extends JavaPlugin { * @param maxMobs

The maximum amount of mobs allowed in the kill zone

* @return

The number of mobs to spawn

*/ - private int calculateMobNumber(Map> questPlayerMap, IQuest quest, IStage stage, int index, - Location killLocation, int radius, int maxMobs) { + private int calculateMobNumber(@NotNull Map> questPlayerMap, @NotNull IQuest quest, + @NotNull IStage stage, int index, @NotNull Location killLocation, int radius, + int maxMobs) { int mobsToSpawn = 0; for (IQuester player : questPlayerMap.get(stage)) { Location playerLocation = player.getPlayer().getLocation().clone(); diff --git a/src/main/java/net/knarcraft/questmobspawns/listener/MobBlockDamageListener.java b/src/main/java/net/knarcraft/questmobspawns/listener/MobBlockDamageListener.java index 7a31577..2a9ac82 100644 --- a/src/main/java/net/knarcraft/questmobspawns/listener/MobBlockDamageListener.java +++ b/src/main/java/net/knarcraft/questmobspawns/listener/MobBlockDamageListener.java @@ -18,17 +18,22 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityInteractEvent; import org.bukkit.event.entity.EntityTargetLivingEntityEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +/** + * A listener for quest mobs causing damage + */ public class MobBlockDamageListener implements Listener { private final QuestsAPI questsAPI; - public MobBlockDamageListener(QuestsAPI questsAPI) { + public MobBlockDamageListener(@NotNull QuestsAPI questsAPI) { this.questsAPI = questsAPI; } @EventHandler(priority = EventPriority.HIGHEST) - public void onExplode(EntityExplodeEvent event) { + public void onExplode(@NotNull EntityExplodeEvent event) { if (QuestMobHelper.isSpawnedMob(event.getEntity())) { event.blockList().clear(); event.setCancelled(false); @@ -36,17 +41,17 @@ public class MobBlockDamageListener implements Listener { } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onBlockChange(EntityChangeBlockEvent event) { + public void onBlockChange(@NotNull EntityChangeBlockEvent event) { cancelIfQuestMob(event, event.getEntity()); } @EventHandler - public void onDoorBreak(EntityBreakDoorEvent event) { + public void onDoorBreak(@NotNull EntityBreakDoorEvent event) { cancelIfQuestMob(event, event.getEntity()); } @EventHandler - public void onEntityTarget(EntityTargetLivingEntityEvent event) { + public void onEntityTarget(@NotNull EntityTargetLivingEntityEvent event) { Entity entity = event.getEntity(); if (!QuestMobHelper.isSpawnedMob(entity) || !(event.getTarget() instanceof Player player)) { return; @@ -62,14 +67,14 @@ public class MobBlockDamageListener implements Listener { event.setCancelled(QuestMobHelper.get2dDistance(player.getLocation(), killLocation) > killLocationData[3]); } } - + @EventHandler - public void onTrample(EntityInteractEvent event) { + public void onTrample(@NotNull EntityInteractEvent event) { cancelIfQuestMob(event, event.getEntity()); } @EventHandler(priority = EventPriority.HIGHEST) - public void onEntityDamage(EntityDamageByEntityEvent event) { + public void onEntityDamage(@NotNull EntityDamageByEntityEvent event) { if (!QuestMobHelper.isSpawnedMob(event.getDamager())) { return; } @@ -84,26 +89,43 @@ public class MobBlockDamageListener implements Listener { /** * Checks whether the given player has the quest the given entity was spawned for - * + * * @param entity

The entity with stored quest data

* @param player

A player

* @return

True if the player has the quest stored in the entity

*/ - private Boolean hasQuest(Entity entity, Player player) { + private Boolean hasQuest(@NotNull Entity entity, @NotNull Player player) { IQuester questPlayer = questsAPI.getQuester(player.getUniqueId()); - IQuest quest = getQuest(QuestMobHelper.getQuestId(entity)); + String questId = QuestMobHelper.getQuestId(entity); + if (questId == null) { + return false; + } + IQuest quest = getQuest(questId); if (quest == null) { // Remove the entity if the quest it belongs to no longer exists entity.remove(); return false; } - IStage stage = quest.getStage(QuestMobHelper.getStageId(entity)); - int index = QuestMobHelper.getIndex(entity); + Integer stageId = QuestMobHelper.getStageId(entity); + if (stageId == null) { + return false; + } + IStage stage = quest.getStage(stageId); + Integer index = QuestMobHelper.getIndex(entity); + if (index == null) { + return false; + } return questPlayer.getQuestData().containsKey(quest) && questPlayer.getCurrentStage(quest) == stage && questPlayer.getQuestData(quest).getMobNumKilled().get(index) < stage.getMobNumToKill().get(index); } - private IQuest getQuest(String questId) { + /** + * Gets the quest with the given id + * + * @param questId

The id of the quest to get

+ * @return

The quest, or null if no such quest exists

+ */ + private @Nullable IQuest getQuest(@NotNull String questId) { for (IQuest quest : questsAPI.getLoadedQuests()) { if (quest.getId().equalsIgnoreCase(questId)) { return quest; @@ -111,8 +133,14 @@ public class MobBlockDamageListener implements Listener { } return null; } - - private void cancelIfQuestMob(Cancellable cancellable, Entity entity) { + + /** + * Cancels a cancellable event if the given entity has been spawned by this plugin + * + * @param cancellable

A cancellable event

+ * @param entity

The entity to check

+ */ + private void cancelIfQuestMob(@NotNull Cancellable cancellable, @NotNull Entity entity) { if (QuestMobHelper.isSpawnedMob(entity)) { cancellable.setCancelled(true); } diff --git a/src/main/java/net/knarcraft/questmobspawns/listener/MobDeathListener.java b/src/main/java/net/knarcraft/questmobspawns/listener/MobDeathListener.java index b3e492e..168f1ac 100644 --- a/src/main/java/net/knarcraft/questmobspawns/listener/MobDeathListener.java +++ b/src/main/java/net/knarcraft/questmobspawns/listener/MobDeathListener.java @@ -6,11 +6,15 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityDropItemEvent; +import org.jetbrains.annotations.NotNull; +/** + * A listener for the death of quest mobs + */ public class MobDeathListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onMobDeath(EntityDeathEvent deathEvent) { + public void onMobDeath(@NotNull EntityDeathEvent deathEvent) { if (QuestMobHelper.isSpawnedMob(deathEvent.getEntity())) { deathEvent.getDrops().clear(); deathEvent.setDroppedExp(0); @@ -18,7 +22,7 @@ public class MobDeathListener implements Listener { } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onMobDrop(EntityDropItemEvent dropEvent) { + public void onMobDrop(@NotNull EntityDropItemEvent dropEvent) { if (QuestMobHelper.isSpawnedMob(dropEvent.getEntity())) { dropEvent.setCancelled(true); } diff --git a/src/main/java/net/knarcraft/questmobspawns/util/QuestMobHelper.java b/src/main/java/net/knarcraft/questmobspawns/util/QuestMobHelper.java index 67dabd3..c49705c 100644 --- a/src/main/java/net/knarcraft/questmobspawns/util/QuestMobHelper.java +++ b/src/main/java/net/knarcraft/questmobspawns/util/QuestMobHelper.java @@ -6,7 +6,12 @@ import org.bukkit.NamespacedKey; import org.bukkit.entity.Entity; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +/** + * A helper class for quest mobs + */ public class QuestMobHelper { private static final NamespacedKey questMobKey = new NamespacedKey(QuestMobSpawns.getPlugin(), "questMob"); @@ -15,23 +20,57 @@ public class QuestMobHelper { private static final NamespacedKey mobIndexKey = new NamespacedKey(QuestMobSpawns.getPlugin(), "mobIndex"); private static final NamespacedKey killZoneLocationKey = new NamespacedKey(QuestMobSpawns.getPlugin(), "killZoneLocationKey"); - public static boolean isSpawnedMob(Entity entity) { + /** + * Checks whether the given entity has been spawned by this plugin + * + * @param entity

The entity to check

+ * @return

True if the entity was spawned by this plugin

+ */ + public static boolean isSpawnedMob(@NotNull Entity entity) { return Boolean.TRUE.equals(entity.getPersistentDataContainer().get(questMobKey, PersistentDataType.BOOLEAN)); } - public static void setSpawnedMob(Entity entity) { + /** + * Marks the given entity as spawned by this plugin + * + * @param entity

The entity to mark

+ */ + public static void setSpawnedMob(@NotNull Entity entity) { entity.getPersistentDataContainer().set(questMobKey, PersistentDataType.BOOLEAN, true); } - public static String getQuestId(Entity entity) { + /** + * Gets the id of the quest the given entity was spawned for + * + * @param entity

The entity to get the quest id from

+ * @return

The quest id, or null if not a quest mob

+ */ + public static @Nullable String getQuestId(@NotNull Entity entity) { return entity.getPersistentDataContainer().get(questIdKey, PersistentDataType.STRING); } - public static Integer getStageId(Entity entity) { + /** + * Gets the id of the stage the given entity was spawned for + * + *

The stage id is relative to the quest the stage belongs to.

+ * + * @param entity

The entity to get the stage id for

+ * @return

The stage id, or null if not a quest mob

+ */ + public static @Nullable Integer getStageId(Entity entity) { return entity.getPersistentDataContainer().get(stageIdKey, PersistentDataType.INTEGER); } - public static Integer getIndex(Entity entity) { + /** + * Gets the index of this entity in the quest's "mobs to kill" array + * + *

This property is necessary in order to check whether the player has already reached the required number of + * mob kills. This matters if the stage has some other clear property other than killing this type of mob.

+ * + * @param entity

The entity to check

+ * @return

The entity's index, or null if not a quest mob

+ */ + public static @Nullable Integer getIndex(@NotNull Entity entity) { return entity.getPersistentDataContainer().get(mobIndexKey, PersistentDataType.INTEGER); } @@ -41,7 +80,7 @@ public class QuestMobHelper { * @param entity

The entity to get data for

* @return

An array containing x,y,z,radius

*/ - public static int[] getKillZoneLocation(Entity entity) { + public static int[] getKillZoneLocation(@NotNull Entity entity) { return entity.getPersistentDataContainer().get(killZoneLocationKey, PersistentDataType.INTEGER_ARRAY); } @@ -52,7 +91,8 @@ public class QuestMobHelper { * @param questId

The id of the quest the mob was spawned for

* @param killLocationInfo

Information about the kill location (x, y, z, range)

*/ - public static void setQuestData(Entity entity, String questId, int stageId, int mobIndex, int[] killLocationInfo) { + public static void setQuestData(@NotNull Entity entity, @NotNull String questId, int stageId, int mobIndex, + int[] killLocationInfo) { PersistentDataContainer persistentDataContainer = entity.getPersistentDataContainer(); persistentDataContainer.set(questIdKey, PersistentDataType.STRING, questId); persistentDataContainer.set(stageIdKey, PersistentDataType.INTEGER, stageId); @@ -67,7 +107,7 @@ public class QuestMobHelper { * @param location2

The second location

* @return

The 2d distance between the locations

*/ - public static double get2dDistance(Location location1, Location location2) { + public static double get2dDistance(@NotNull Location location1, @NotNull Location location2) { return Math.sqrt(Math.pow(location2.getX() - location1.getX(), 2) + Math.pow(location2.getZ() - location1.getZ(), 2)); }