From b10dacab52c1c59ff657c00e3e33f07eccdf5fb7 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Tue, 1 Nov 2022 04:16:29 +0100 Subject: [PATCH] Adds support for multi-quest quest giver NPCs Adds information about all provided quests to a quest NPC's marker Makes sure to use the most appropriate marker title and icon based on which role the NPC fulfills Improves HTML format of quest marker descriptions --- .../dynmapcitizens/DynmapCitizens.java | 3 +- .../net/knarcraft/dynmapcitizens/Icon.java | 7 +- .../trait/quests/NPCQuestInfo.java | 126 +++++++++++ .../trait/quests/QuestNPCType.java | 33 +++ .../trait/{ => quests}/QuestsHandler.java | 202 +++++++++++------- 5 files changed, 296 insertions(+), 75 deletions(-) create mode 100644 src/main/java/net/knarcraft/dynmapcitizens/trait/quests/NPCQuestInfo.java create mode 100644 src/main/java/net/knarcraft/dynmapcitizens/trait/quests/QuestNPCType.java rename src/main/java/net/knarcraft/dynmapcitizens/trait/{ => quests}/QuestsHandler.java (61%) diff --git a/src/main/java/net/knarcraft/dynmapcitizens/DynmapCitizens.java b/src/main/java/net/knarcraft/dynmapcitizens/DynmapCitizens.java index 8f8d2d9..883a95f 100644 --- a/src/main/java/net/knarcraft/dynmapcitizens/DynmapCitizens.java +++ b/src/main/java/net/knarcraft/dynmapcitizens/DynmapCitizens.java @@ -2,8 +2,8 @@ package net.knarcraft.dynmapcitizens; import net.knarcraft.dynmapcitizens.trait.BlacksmithHandler; import net.knarcraft.dynmapcitizens.trait.CitizensTraitHandler; -import net.knarcraft.dynmapcitizens.trait.QuestsHandler; import net.knarcraft.dynmapcitizens.trait.SentinelHandler; +import net.knarcraft.dynmapcitizens.trait.quests.QuestsHandler; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; @@ -114,6 +114,7 @@ public final class DynmapCitizens extends JavaPlugin { markerIcons.put(Icon.QUEST_INTERACT, markerAPI.getMarkerIcon("comment")); markerIcons.put(Icon.BLACKSMITH, markerAPI.getMarkerIcon("hammer")); markerIcons.put(Icon.SENTINEL, markerAPI.getMarkerIcon("shield")); + markerIcons.put(Icon.QUEST_CHAIN, markerAPI.getMarkerIcon("caution")); this.markerIcons = markerIcons; } diff --git a/src/main/java/net/knarcraft/dynmapcitizens/Icon.java b/src/main/java/net/knarcraft/dynmapcitizens/Icon.java index 20e79a7..c0fc5fa 100644 --- a/src/main/java/net/knarcraft/dynmapcitizens/Icon.java +++ b/src/main/java/net/knarcraft/dynmapcitizens/Icon.java @@ -11,7 +11,7 @@ public enum Icon { QUEST_GIVER, /** - * An icon representing an interactable NPC part of some quest + * An icon representing an interact-able NPC part of some quest */ QUEST_INTERACT, @@ -25,6 +25,11 @@ public enum Icon { */ QUEST_DELIVER, + /** + * An icon representing an NPC part of a quest chain + */ + QUEST_CHAIN, + /** * An icon representing a sentinel NPC */ diff --git a/src/main/java/net/knarcraft/dynmapcitizens/trait/quests/NPCQuestInfo.java b/src/main/java/net/knarcraft/dynmapcitizens/trait/quests/NPCQuestInfo.java new file mode 100644 index 0000000..89ba09d --- /dev/null +++ b/src/main/java/net/knarcraft/dynmapcitizens/trait/quests/NPCQuestInfo.java @@ -0,0 +1,126 @@ +package net.knarcraft.dynmapcitizens.trait.quests; + +import me.blackvein.quests.quests.IQuest; +import net.knarcraft.dynmapcitizens.Icon; + +import java.util.ArrayList; +import java.util.List; + +/** + * Keeps track of quest information for an NPC + */ +public class NPCQuestInfo { + + private final List questStart = new ArrayList<>(); + private final List questKill = new ArrayList<>(); + private final List questDeliver = new ArrayList<>(); + private final List questInteract = new ArrayList<>(); + + /** + * Adds the given quest to the list of quests this NPC provides + * + * @param quest

The quest to add

+ */ + public void addQuestStart(IQuest quest) { + this.questStart.add(quest); + } + + /** + * Adds the given quest to the list of quests in which this NPC is killed + * + * @param quest

The quest to add

+ */ + public void addQuestKill(IQuest quest) { + this.questKill.add(quest); + } + + /** + * Adds the given quest to the list of quests in which this NPC takes deliveries + * + * @param quest

The quest to add

+ */ + public void addQuestDeliver(IQuest quest) { + this.questDeliver.add(quest); + } + + /** + * Adds the given quest to the list of quests in which this NPC has to be interacted with + * + * @param quest

The quest to add

+ */ + public void addQuestInteract(IQuest quest) { + this.questInteract.add(quest); + } + + /** + * Gets all quests given by this NPC + * + * @return

All quests this NPC is used to start

+ */ + public List getQuestStarts() { + return new ArrayList<>(this.questStart); + } + + /** + * Gets all quests in which this NPC has to be killed + * + * @return

All kill quests involving this NPC

+ */ + public List getQuestKills() { + return new ArrayList<>(this.questKill); + } + + /** + * Gets all quests requiring something be delivered to this NPC + * + * @return

All quests delivering to this NPC

+ */ + public List getQuestDeliveries() { + return new ArrayList<>(this.questDeliver); + } + + /** + * Gets all quests requiring interaction with this NPC + * + * @return

All quests requiring interaction with this NPC

+ */ + public List getQuestInteractions() { + return new ArrayList<>(this.questInteract); + } + + /** + * Gets the main type of the quest the NPC + * + * @return

The main type of the quest NPC

+ */ + public QuestNPCType getQuestNPCType() { + if (!questStart.isEmpty()) { + return QuestNPCType.GIVER; + } else if (!questKill.isEmpty() && questInteract.isEmpty() && questDeliver.isEmpty()) { + return QuestNPCType.KILL; + } else if (!questInteract.isEmpty() && questKill.isEmpty() && questDeliver.isEmpty()) { + return QuestNPCType.INTERACT; + } else if (!questDeliver.isEmpty() && questKill.isEmpty() && questInteract.isEmpty()) { + return QuestNPCType.DELIVER; + } else { + return QuestNPCType.CHAIN; + } + } + + /** + * Gets the Icon to use when marking this NPC + * + * @return

The icon used to mark this NPC

+ */ + public Icon getNPCIcon() { + QuestNPCType type = getQuestNPCType(); + return switch (type) { + case KILL -> Icon.QUEST_KILL; + case GIVER -> Icon.QUEST_GIVER; + case DELIVER -> Icon.QUEST_DELIVER; + case INTERACT -> Icon.QUEST_INTERACT; + case CHAIN -> Icon.QUEST_CHAIN; + }; + } + +} diff --git a/src/main/java/net/knarcraft/dynmapcitizens/trait/quests/QuestNPCType.java b/src/main/java/net/knarcraft/dynmapcitizens/trait/quests/QuestNPCType.java new file mode 100644 index 0000000..918bea6 --- /dev/null +++ b/src/main/java/net/knarcraft/dynmapcitizens/trait/quests/QuestNPCType.java @@ -0,0 +1,33 @@ +package net.knarcraft.dynmapcitizens.trait.quests; + +/** + * A specifier for a quest NPC's main type + */ +public enum QuestNPCType { + + /** + * An NPC responsible for giving quests + */ + GIVER, + + /** + * An NPC killed in a quest + */ + KILL, + + /** + * An NPC set as the delivery target in a quest + */ + DELIVER, + + /** + * An NPC to be interacted with in a quest + */ + INTERACT, + + /** + * An NPC which is not a quest giver, but has several tasks as part of other quests + */ + CHAIN + +} diff --git a/src/main/java/net/knarcraft/dynmapcitizens/trait/QuestsHandler.java b/src/main/java/net/knarcraft/dynmapcitizens/trait/quests/QuestsHandler.java similarity index 61% rename from src/main/java/net/knarcraft/dynmapcitizens/trait/QuestsHandler.java rename to src/main/java/net/knarcraft/dynmapcitizens/trait/quests/QuestsHandler.java index 587c2be..fd232d7 100644 --- a/src/main/java/net/knarcraft/dynmapcitizens/trait/QuestsHandler.java +++ b/src/main/java/net/knarcraft/dynmapcitizens/trait/quests/QuestsHandler.java @@ -1,4 +1,4 @@ -package net.knarcraft.dynmapcitizens.trait; +package net.knarcraft.dynmapcitizens.trait.quests; import me.blackvein.quests.QuestsAPI; import me.blackvein.quests.quests.IQuest; @@ -9,6 +9,7 @@ import net.citizensnpcs.api.npc.NPCRegistry; import net.knarcraft.dynmapcitizens.DynmapCitizens; import net.knarcraft.dynmapcitizens.Icon; import net.knarcraft.dynmapcitizens.UpdateRate; +import net.knarcraft.dynmapcitizens.trait.AbstractTraitHandler; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -19,7 +20,10 @@ import org.dynmap.markers.GenericMarker; import org.dynmap.markers.MarkerIcon; import org.dynmap.markers.MarkerSet; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.UUID; @@ -35,6 +39,7 @@ public class QuestsHandler extends AbstractTraitHandler { private MarkerSet questAreaMarkerSet; private Map markerIcons; private Collection loadedQuests; + private Map questGiverInfo; @Override public void initialize() { @@ -67,6 +72,8 @@ public class QuestsHandler extends AbstractTraitHandler { return; } + questGiverInfo = new HashMap<>(); + //There is no point in updating if there has been no changes in quests boolean questsChanged = loadedQuests == null || !loadedQuests.equals(questsAPI.getLoadedQuests()); loadedQuests = questsAPI.getLoadedQuests(); @@ -79,34 +86,109 @@ public class QuestsHandler extends AbstractTraitHandler { updateQuestAreas(); } - //TODO: The order of operations seems kind of wrong right now. To get an actual overview of which Quests NPCs - // are involved in, it's probably best to loop through all NPCs instead, as each NPC can be the start for - // several quests. Might be inefficient though. No matter what, NPC-quest-relations should probably be stored - // somehow, and only printed once everything's been calculated. For each NPC, store a list of quests they can - // be used to start, a list of quests they are used as delivery target for and a list of quests in which they - // must be killed. So, a dictionary from unique NPC id to a quest info object. The object stores everything - // necessary about quests. When finished, loop through the dictionary, and generate the marker, with all the - // quest info. I'm not sure at which point the quest area markers should be generated. + NPCRegistry registry = CitizensAPI.getNPCRegistry(); + //Generation information about NPC's parts in each quest for (IQuest quest : questsAPI.getLoadedQuests()) { - UUID npcStartId = quest.getNpcStart(); - //TODO: Store locations of NPCs and see if they've moved? - // there should probably be a priority and a combination for NPC markers. - // Use the npc ID as the marker id. If it already exists, try and add additional information. Use priority - // to decide if icon needs to be changed. - if (npcStartId != null) { - String markerDescription = "Quest name: " + quest.getName() + "
Quest description: " + - quest.getDescription() + getQuestStagesInfo(quest); - addNPCMarker(npcStartId, "Quest Start NPC: ", markerDescription, - markerIcons.get(Icon.QUEST_GIVER), questMarkerSet); + if (quest.getNpcStart() != null) { + getInfo(quest.getNpcStart()).addQuestStart(quest); } - //TODO: Perhaps display requirements and rewards as part of the description - - //Mark NPCs related to this quest for (IStage stage : quest.getStages()) { - markNPCsInQuest(quest, stage, npcStartId); + for (UUID npcId : stage.getNpcsToKill()) { + getInfo(npcId).addQuestKill(quest); + } + for (UUID npcId : stage.getItemDeliveryTargets()) { + getInfo(npcId).addQuestDeliver(quest); + } + for (UUID npcId : stage.getNpcsToInteract()) { + getInfo(npcId).addQuestInteract(quest); + } } } + + //Add markers for each NPC detected as part of a quest + for (UUID npcId : questGiverInfo.keySet()) { + NPCQuestInfo info = questGiverInfo.get(npcId); + MarkerIcon icon = markerIcons.get(info.getNPCIcon()); + List questStarts = info.getQuestStarts(); + List questKills = info.getQuestKills(); + List questInteractions = info.getQuestInteractions(); + List questDeliveries = info.getQuestDeliveries(); + StringBuilder markerDescription = new StringBuilder(); + + markerDescription.append("Quest NPC Name: ").append(registry.getByUniqueId(npcId).getName()); + + if (!questStarts.isEmpty()) { + markerDescription.append("

Quests offered:

    "); + for (IQuest quest : questStarts) { + markerDescription.append("
  • Quest name: ").append(quest.getName()).append( + "
    Quest description: ").append(quest.getDescription()).append( + getQuestStagesInfo(quest)).append("
  • "); + } + markerDescription.append("
"); + } + + if (!questKills.isEmpty()) { + markerDescription.append("

Kill target for quests:

"); + markerDescription.append(getQuestListString(questKills)); + } + + if (!questInteractions.isEmpty()) { + markerDescription.append("

Interaction target for quests:

"); + markerDescription.append(getQuestListString(questInteractions)); + } + + if (!questDeliveries.isEmpty()) { + markerDescription.append("

Delivery target for quests:

"); + markerDescription.append(getQuestListString(questDeliveries)); + } + + addNPCMarker(npcId, getMarkerTitle(info.getQuestNPCType()), markerDescription.toString(), icon, questMarkerSet); + } + } + + /** + * Gets a comma-separated list of the given quests' names + * + * @param quests

The quests to get a list string for

+ * @return

The names of the given quests

+ */ + private String getQuestListString(List quests) { + List questNames = new ArrayList<>(); + //Note: The list is cast to a set to remove duplicates + for (IQuest quest : new HashSet<>(quests)) { + questNames.add(quest.getName()); + } + return String.join(", ", questNames); + } + + /** + * Gets the marker title to use for the given quest NPC type + * + * @param type

The type of marker to get the title for

+ * @return

The title to use for the marker

+ */ + private String getMarkerTitle(QuestNPCType type) { + return switch (type) { + case GIVER -> "Quest Start NPC: "; + case INTERACT -> "Quest Interact NPC: "; + case DELIVER -> "Quest Deliver NPC: "; + case KILL -> "Quest Kill NPC: "; + case CHAIN -> "Quest Chain NPC: "; + }; + } + + /** + * Gets the info object for the given NPC + * + * @param npcId

The id of the NPC to get information about

+ * @return

The NPC's info object

+ */ + private NPCQuestInfo getInfo(UUID npcId) { + if (questGiverInfo.get(npcId) == null) { + questGiverInfo.put(npcId, new NPCQuestInfo()); + } + return questGiverInfo.get(npcId); } /** @@ -134,51 +216,53 @@ public class QuestsHandler extends AbstractTraitHandler { StringBuilder questInfo = new StringBuilder(); NPCRegistry registry = CitizensAPI.getNPCRegistry(); for (IStage stage : quest.getStages()) { - questInfo.append("
Tasks: "); + questInfo.append("
Tasks:
    "); int mobTypes = stage.getMobsToKill().size(); for (int i = 0; i < mobTypes; i++) { - questInfo.append("
    Kill ").append(normalizeName(stage.getMobsToKill().get(i).name())).append( - " X ").append(stage.getMobNumToKill().get(i)); + questInfo.append("
  • Kill ").append(normalizeName(stage.getMobsToKill().get(i).name())).append( + " X ").append(stage.getMobNumToKill().get(i)).append("
  • "); } int deliveries = stage.getItemDeliveryTargets().size(); for (int i = 0; i < deliveries; i++) { NPC npc = registry.getByUniqueId(stage.getItemDeliveryTargets().get(i)); - questInfo.append("
    Deliver ").append(getItemStackString(stage.getItemsToDeliver().get(i))).append( - " to ").append(npc.getName()); + questInfo.append("
  • Deliver ").append(getItemStackString(stage.getItemsToDeliver().get(i))).append( + " to ").append(npc.getName()).append("
  • "); } if (stage.getFishToCatch() != null) { - questInfo.append("
    Catch ").append(stage.getFishToCatch()).append(" fish"); + questInfo.append("
  • Catch ").append(stage.getFishToCatch()).append(" fish").append("
  • "); } for (UUID npcId : stage.getNpcsToKill()) { - questInfo.append("
    Kill NPC ").append(registry.getByUniqueId(npcId).getName()); + questInfo.append("
  • Kill NPC ").append(registry.getByUniqueId(npcId).getName()).append("
  • "); } - questInfo.append(getQuestItemsTaskString(stage.getBlocksToBreak(), "
    Break ")); - questInfo.append(getQuestItemsTaskString(stage.getBlocksToCut(), "
    Cut ")); - questInfo.append(getQuestItemsTaskString(stage.getBlocksToDamage(), "
    Damage ")); - questInfo.append(getQuestItemsTaskString(stage.getBlocksToUse(), "
    Use ")); - questInfo.append(getQuestItemsTaskString(stage.getBlocksToPlace(), "
    Place ")); + questInfo.append(getQuestItemsTaskString(stage.getBlocksToBreak(), "
  • Break ")).append("
  • "); + questInfo.append(getQuestItemsTaskString(stage.getBlocksToCut(), "
  • Cut ")).append("
  • "); + questInfo.append(getQuestItemsTaskString(stage.getBlocksToDamage(), "
  • Damage ")).append("
  • "); + questInfo.append(getQuestItemsTaskString(stage.getBlocksToUse(), "
  • Use ")).append("
  • "); + questInfo.append(getQuestItemsTaskString(stage.getBlocksToPlace(), "
  • Place ")).append("
  • "); - questInfo.append(getQuestItemsTaskString(stage.getItemsToBrew(), "
    Brew ")); - questInfo.append(getQuestItemsTaskString(stage.getItemsToConsume(), "
    Consume ")); - questInfo.append(getQuestItemsTaskString(stage.getItemsToCraft(), "
    Craft ")); - questInfo.append(getQuestItemsTaskString(stage.getItemsToEnchant(), "
    Enchant ")); - questInfo.append(getQuestItemsTaskString(stage.getItemsToSmelt(), "
    Smelt ")); + questInfo.append(getQuestItemsTaskString(stage.getItemsToBrew(), "
  • Brew ")).append("
  • "); + questInfo.append(getQuestItemsTaskString(stage.getItemsToConsume(), "
  • Consume ")).append("
  • "); + questInfo.append(getQuestItemsTaskString(stage.getItemsToCraft(), "
  • Craft ")).append("
  • "); + questInfo.append(getQuestItemsTaskString(stage.getItemsToEnchant(), "
  • Enchant ")).append("
  • "); + questInfo.append(getQuestItemsTaskString(stage.getItemsToSmelt(), "
  • Smelt ")).append("
  • "); int sheepTypes = stage.getSheepToShear().size(); for (int i = 0; i < sheepTypes; i++) { - questInfo.append("
    Shear ").append(stage.getSheepNumToShear().get(i)).append(" ").append( - normalizeName(stage.getSheepToShear().get(i).name())).append(" sheep"); + questInfo.append("
  • Shear ").append(stage.getSheepNumToShear().get(i)).append(" ").append( + normalizeName(stage.getSheepToShear().get(i).name())).append(" sheep").append("
  • "); } if (stage.getCowsToMilk() != null) { - questInfo.append("
    Milk ").append(stage.getCowsToMilk()).append(" cows"); + questInfo.append("
  • Milk ").append(stage.getCowsToMilk()).append(" cows").append("
  • "); } int mobTamingEntries = stage.getMobsToTame().size(); for (int i = 0; i < mobTamingEntries; i++) { - questInfo.append("
    Tame ").append(stage.getMobNumToTame().get(i)).append(" ").append( - normalizeName(stage.getMobsToTame().get(i).name())); + questInfo.append("
  • Tame ").append(stage.getMobNumToTame().get(i)).append(" ").append( + normalizeName(stage.getMobsToTame().get(i).name())).append("
  • "); } + + questInfo.append("
"); } return questInfo.toString(); } @@ -270,32 +354,4 @@ public class QuestsHandler extends AbstractTraitHandler { } } - /** - * Marks all NPCs which has interactions as part of this stage - * - * @param quest

The quest to mark NPCs for

- * @param stage

The quest stage to mark NPCs for

- * @param npcStartId

The id of the quest's start NPC

- */ - private void markNPCsInQuest(IQuest quest, IStage stage, UUID npcStartId) { - String markerDescription = "Part of quest: " + quest.getName(); - //Mark all interaction targets - for (UUID interactNpcId : stage.getNpcsToInteract()) { - addNPCMarker(interactNpcId, "Quest Interact NPC: ", markerDescription, - markerIcons.get(Icon.QUEST_INTERACT), questMarkerSet); - } - //Mark all kill targets - for (UUID killNPCId : stage.getNpcsToKill()) { - addNPCMarker(killNPCId, "Quest Kill NPC: ", markerDescription, markerIcons.get(Icon.QUEST_KILL), - questMarkerSet); - } - //Mark all delivery targets - for (UUID deliveryTargetNPCId : stage.getItemDeliveryTargets()) { - if (!deliveryTargetNPCId.equals(npcStartId)) { - addNPCMarker(deliveryTargetNPCId, "Quest Delivery NPC: ", markerDescription, - markerIcons.get(Icon.QUEST_DELIVER), questMarkerSet); - } - } - } - }