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
This commit is contained in:
Kristian Knarvik 2022-11-01 04:16:29 +01:00
parent a21130001c
commit b10dacab52
5 changed files with 296 additions and 75 deletions

View File

@ -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;
}

View File

@ -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
*/

View File

@ -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<IQuest> questStart = new ArrayList<>();
private final List<IQuest> questKill = new ArrayList<>();
private final List<IQuest> questDeliver = new ArrayList<>();
private final List<IQuest> questInteract = new ArrayList<>();
/**
* Adds the given quest to the list of quests this NPC provides
*
* @param quest <p>The quest to add</p>
*/
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 <p>The quest to add</p>
*/
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 <p>The quest to add</p>
*/
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 <p>The quest to add</p>
*/
public void addQuestInteract(IQuest quest) {
this.questInteract.add(quest);
}
/**
* Gets all quests given by this NPC
*
* @return <p>All quests this NPC is used to start</p>
*/
public List<IQuest> getQuestStarts() {
return new ArrayList<>(this.questStart);
}
/**
* Gets all quests in which this NPC has to be killed
*
* @return <p>All kill quests involving this NPC</p>
*/
public List<IQuest> getQuestKills() {
return new ArrayList<>(this.questKill);
}
/**
* Gets all quests requiring something be delivered to this NPC
*
* @return <p>All quests delivering to this NPC</p>
*/
public List<IQuest> getQuestDeliveries() {
return new ArrayList<>(this.questDeliver);
}
/**
* Gets all quests requiring interaction with this NPC
*
* @return <p>All quests requiring interaction with this NPC</p>
*/
public List<IQuest> getQuestInteractions() {
return new ArrayList<>(this.questInteract);
}
/**
* Gets the main type of the quest the NPC
*
* @return <p>The main type of the quest NPC</p>
*/
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 <p>The icon used to mark this NPC</p>
*/
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;
};
}
}

View File

@ -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
}

View File

@ -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<Icon, MarkerIcon> markerIcons;
private Collection<IQuest> loadedQuests;
private Map<UUID, NPCQuestInfo> 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 = "<b>Quest name:</b> " + quest.getName() + "<br><b>Quest description:</b> " +
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<IQuest> questStarts = info.getQuestStarts();
List<IQuest> questKills = info.getQuestKills();
List<IQuest> questInteractions = info.getQuestInteractions();
List<IQuest> questDeliveries = info.getQuestDeliveries();
StringBuilder markerDescription = new StringBuilder();
markerDescription.append("<b>Quest NPC Name:</b> ").append(registry.getByUniqueId(npcId).getName());
if (!questStarts.isEmpty()) {
markerDescription.append("<br><h3>Quests offered:</h3><ul>");
for (IQuest quest : questStarts) {
markerDescription.append("<li><b>Quest name:</b> ").append(quest.getName()).append(
"<br><b>Quest description:</b> ").append(quest.getDescription()).append(
getQuestStagesInfo(quest)).append("</li>");
}
markerDescription.append("</ul>");
}
if (!questKills.isEmpty()) {
markerDescription.append("<h3>Kill target for quests:</h3>");
markerDescription.append(getQuestListString(questKills));
}
if (!questInteractions.isEmpty()) {
markerDescription.append("<h3>Interaction target for quests:</h3>");
markerDescription.append(getQuestListString(questInteractions));
}
if (!questDeliveries.isEmpty()) {
markerDescription.append("<h3>Delivery target for quests:</h3>");
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 <p>The quests to get a list string for</p>
* @return <p>The names of the given quests</p>
*/
private String getQuestListString(List<IQuest> quests) {
List<String> 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 <p>The type of marker to get the title for</p>
* @return <p>The title to use for the marker</p>
*/
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 <p>The id of the NPC to get information about</p>
* @return <p>The NPC's info object</p>
*/
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("<br><b>Tasks:</b> ");
questInfo.append("<br><b>Tasks:</b><ul>");
int mobTypes = stage.getMobsToKill().size();
for (int i = 0; i < mobTypes; i++) {
questInfo.append("<br>Kill ").append(normalizeName(stage.getMobsToKill().get(i).name())).append(
" X ").append(stage.getMobNumToKill().get(i));
questInfo.append("<li>Kill ").append(normalizeName(stage.getMobsToKill().get(i).name())).append(
" X ").append(stage.getMobNumToKill().get(i)).append("</li>");
}
int deliveries = stage.getItemDeliveryTargets().size();
for (int i = 0; i < deliveries; i++) {
NPC npc = registry.getByUniqueId(stage.getItemDeliveryTargets().get(i));
questInfo.append("<br>Deliver ").append(getItemStackString(stage.getItemsToDeliver().get(i))).append(
" to ").append(npc.getName());
questInfo.append("<li>Deliver ").append(getItemStackString(stage.getItemsToDeliver().get(i))).append(
" to ").append(npc.getName()).append("</li>");
}
if (stage.getFishToCatch() != null) {
questInfo.append("<br>Catch ").append(stage.getFishToCatch()).append(" fish");
questInfo.append("<li>Catch ").append(stage.getFishToCatch()).append(" fish").append("</li>");
}
for (UUID npcId : stage.getNpcsToKill()) {
questInfo.append("<br>Kill NPC ").append(registry.getByUniqueId(npcId).getName());
questInfo.append("<li>Kill NPC ").append(registry.getByUniqueId(npcId).getName()).append("</li>");
}
questInfo.append(getQuestItemsTaskString(stage.getBlocksToBreak(), "<br>Break "));
questInfo.append(getQuestItemsTaskString(stage.getBlocksToCut(), "<br>Cut "));
questInfo.append(getQuestItemsTaskString(stage.getBlocksToDamage(), "<br>Damage "));
questInfo.append(getQuestItemsTaskString(stage.getBlocksToUse(), "<br>Use "));
questInfo.append(getQuestItemsTaskString(stage.getBlocksToPlace(), "<br>Place "));
questInfo.append(getQuestItemsTaskString(stage.getBlocksToBreak(), "<li>Break ")).append("</li>");
questInfo.append(getQuestItemsTaskString(stage.getBlocksToCut(), "<li>Cut ")).append("</li>");
questInfo.append(getQuestItemsTaskString(stage.getBlocksToDamage(), "<li>Damage ")).append("</li>");
questInfo.append(getQuestItemsTaskString(stage.getBlocksToUse(), "<li>Use ")).append("</li>");
questInfo.append(getQuestItemsTaskString(stage.getBlocksToPlace(), "<li>Place ")).append("</li>");
questInfo.append(getQuestItemsTaskString(stage.getItemsToBrew(), "<br>Brew "));
questInfo.append(getQuestItemsTaskString(stage.getItemsToConsume(), "<br>Consume "));
questInfo.append(getQuestItemsTaskString(stage.getItemsToCraft(), "<br>Craft "));
questInfo.append(getQuestItemsTaskString(stage.getItemsToEnchant(), "<br>Enchant "));
questInfo.append(getQuestItemsTaskString(stage.getItemsToSmelt(), "<br>Smelt "));
questInfo.append(getQuestItemsTaskString(stage.getItemsToBrew(), "<li>Brew ")).append("</li>");
questInfo.append(getQuestItemsTaskString(stage.getItemsToConsume(), "<li>Consume ")).append("</li>");
questInfo.append(getQuestItemsTaskString(stage.getItemsToCraft(), "<li>Craft ")).append("</li>");
questInfo.append(getQuestItemsTaskString(stage.getItemsToEnchant(), "<li>Enchant ")).append("</li>");
questInfo.append(getQuestItemsTaskString(stage.getItemsToSmelt(), "<li>Smelt ")).append("</li>");
int sheepTypes = stage.getSheepToShear().size();
for (int i = 0; i < sheepTypes; i++) {
questInfo.append("<br>Shear ").append(stage.getSheepNumToShear().get(i)).append(" ").append(
normalizeName(stage.getSheepToShear().get(i).name())).append(" sheep");
questInfo.append("<li>Shear ").append(stage.getSheepNumToShear().get(i)).append(" ").append(
normalizeName(stage.getSheepToShear().get(i).name())).append(" sheep").append("</li>");
}
if (stage.getCowsToMilk() != null) {
questInfo.append("<br>Milk ").append(stage.getCowsToMilk()).append(" cows");
questInfo.append("<li>Milk ").append(stage.getCowsToMilk()).append(" cows").append("</li>");
}
int mobTamingEntries = stage.getMobsToTame().size();
for (int i = 0; i < mobTamingEntries; i++) {
questInfo.append("<br>Tame ").append(stage.getMobNumToTame().get(i)).append(" ").append(
normalizeName(stage.getMobsToTame().get(i).name()));
questInfo.append("<li>Tame ").append(stage.getMobNumToTame().get(i)).append(" ").append(
normalizeName(stage.getMobsToTame().get(i).name())).append("</li>");
}
questInfo.append("</ul>");
}
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 <p>The quest to mark NPCs for</p>
* @param stage <p>The quest stage to mark NPCs for</p>
* @param npcStartId <p>The id of the quest's start NPC</p>
*/
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);
}
}
}
}