From d238497ee390e9dc275685e2f15eec407ba40b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 22 Oct 2024 00:07:52 +0200 Subject: [PATCH 01/36] add quest serialization --- .../gameserver/dialog/DialogSection.java | 10 ++ .../gameserver/entity/EntityConfig.java | 9 + .../brainwine/gameserver/player/Player.java | 10 ++ .../gameserver/player/PlayerConfigFile.java | 8 + .../gameserver/quest/HardcodedQuest.java | 12 ++ .../gameserver/quest/PlayerQuestDialog.java | 111 ++++++++++++ .../gameserver/quest/PlayerQuests.java | 152 ++++++++++++++++ .../brainwine/gameserver/quest/Quest.java | 137 +++++++++++++++ .../gameserver/quest/QuestAction.java | 112 ++++++++++++ .../gameserver/quest/QuestEvents.java | 142 +++++++++++++++ .../gameserver/quest/QuestProgress.java | 164 ++++++++++++++++++ .../gameserver/quest/QuestReward.java | 45 +++++ .../gameserver/quest/QuestStory.java | 46 +++++ .../brainwine/gameserver/quest/QuestTask.java | 123 +++++++++++++ .../quest/QuestTaskCollectInventory.java | 63 +++++++ .../brainwine/gameserver/quest/Quests.java | 147 ++++++++++++++++ .../server/messages/QuestMessage.java | 18 ++ .../brainwine/gameserver/util/PickRandom.java | 55 ++++++ 18 files changed, 1364 insertions(+) create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/HardcodedQuest.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuestDialog.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/Quest.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/QuestAction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/QuestStory.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/Quests.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/server/messages/QuestMessage.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/PickRandom.java diff --git a/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java b/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java index 5f6f48f2..7cc342f9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java +++ b/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java @@ -24,6 +24,7 @@ public class DialogSection { private double textScale; private Vector2i location; private DialogInput input; + private String choice; public DialogSection addItem(DialogListItem item) { items.add(item); @@ -100,4 +101,13 @@ public DialogSection setInput(DialogInput input) { public DialogInput getInput() { return input; } + + public DialogSection setChoice(String choice) { + this.choice = choice; + return this; + } + + public String getChoice() { + return this.choice; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java index fdb43f88..4ee75842 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; @@ -68,6 +69,14 @@ public String getName() { public int getType() { return type; } + + @JsonIgnore + public String getCategory() { + String id = getName(); + + int index = id.indexOf('/'); + return index > 1 ? id.substring(0, index) : null; + } @JsonProperty("xp") public int getExperienceYield() { diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index bed73341..c446f232 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -38,6 +38,8 @@ import brainwine.gameserver.item.MiningBonus; import brainwine.gameserver.item.consumables.Consumable; import brainwine.gameserver.loot.Loot; +import brainwine.gameserver.quest.QuestEvents; +import brainwine.gameserver.quest.QuestProgress; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.messages.AchievementMessage; import brainwine.gameserver.server.messages.AchievementProgressMessage; @@ -104,6 +106,7 @@ public class Player extends Entity implements CommandExecutor { private Map skills; private Map> bumpedSkills; private Map appearance; + private Map questProgresses = new HashMap<>(); private final Map settings = new HashMap<>(); private final Set activeChunks = new HashSet<>(); private final Map> dialogs = new HashMap<>(); @@ -148,6 +151,7 @@ protected Player(String documentId, PlayerConfigFile config) { this.skills = config.getSkills(); this.bumpedSkills = config.getBumpedSkills(); this.appearance = config.getAppearance(); + this.questProgresses = config.getQuestProgresses(); health = getMaxHealth(); inventory.setPlayer(this); statistics.setPlayer(this); @@ -472,6 +476,7 @@ public void changeZone(Zone zone, int x, int y) { customSpawn = x != -1 && y != -1; sendMessage(new EventMessage("playerWillChangeZone", null)); kick("Teleporting...", true); + QuestEvents.handleEnterZone(this, zone); } public void showDialog(Dialog dialog) { @@ -1120,11 +1125,16 @@ public void randomizeAppearance() { public void updateAppearance(Map appearance) { this.appearance.putAll(appearance); zone.sendMessage(new EntityChangeMessage(id, appearance)); + QuestEvents.handleAppearance(this, appearance); } public Map getAppearance() { return Collections.unmodifiableMap(appearance); } + + public Map getQuestProgresses() { + return questProgresses; + } public void setSkillLevel(Skill skill, int level) { skills.put(skill, level); diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java index e91d43c1..cce241d6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java @@ -14,6 +14,7 @@ import brainwine.gameserver.achievement.Achievement; import brainwine.gameserver.item.Item; +import brainwine.gameserver.quest.QuestProgress; import brainwine.gameserver.zone.Zone; @JsonIgnoreProperties(ignoreUnknown = true) @@ -40,6 +41,7 @@ public class PlayerConfigFile { private Map skills = new HashMap<>(); private Map> bumpedSkills = new HashMap<>(); private Map appearance = new HashMap<>(); + private Map questProgresses = new HashMap<>(); public PlayerConfigFile(Player player) { this.name = player.getName(); @@ -63,6 +65,7 @@ public PlayerConfigFile(Player player) { this.skills = player.getSkills(); this.bumpedSkills = player.getBumpedSkills(); this.appearance = player.getAppearance(); + this.questProgresses = player.getQuestProgresses(); } @JsonCreator @@ -164,4 +167,9 @@ public Map> getBumpedSkills() { public Map getAppearance() { return appearance; } + + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + public Map getQuestProgresses() { + return questProgresses; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/HardcodedQuest.java b/gameserver/src/main/java/brainwine/gameserver/quest/HardcodedQuest.java new file mode 100644 index 00000000..1f4c9984 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/HardcodedQuest.java @@ -0,0 +1,12 @@ +package brainwine.gameserver.quest; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class HardcodedQuest { + @JsonProperty("quest") + private String questId; + + public String getQuestId() { + return questId; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuestDialog.java b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuestDialog.java new file mode 100644 index 00000000..d4445887 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuestDialog.java @@ -0,0 +1,111 @@ +package brainwine.gameserver.quest; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.player.Player; + +public class PlayerQuestDialog { + private PlayerQuestDialog() {} + + public static Dialog questOffersDialogGet(List quests) { + Dialog result = new Dialog().setTitle("My Quest Offers"); + + result.addSection(new DialogSection().setText("Here are my quest offers for you:")); + for(Quest quest : quests) { + DialogSection section = new DialogSection().setText(quest.getTitle()).setChoice(quest.getId()); + + result.addSection(section); + } + + result.setActions("Cancel"); + + return result; + } + + public static Quest questOffersDialogGetSelectedOffer(Object[] ans) { + if(ans.length == 0) return null; + if("cancel".equals(ans[0])) return null; + + else return Quests.get((String) ans[0]); + } + + public static Dialog confirmBeginQuestDialogGet(Quest quest) { + Dialog result = new Dialog().setTitle(quest.getTitle()); + + result.addSection(new DialogSection().setText(quest.getStory().getIntro())); + + result.setActions("Cancel", quest.getStory().getAccept()); + + return result; + } + + public static Dialog beginQuestDialogGet(Player player, Quest quest) { + return DialogHelper.messageDialog(player.isV3() ? quest.getStory().getBegin() : quest.getStory().getBeginMobile()); + } + + public static Dialog playerQuestsDialogGet(Player player) { + Dialog result = new Dialog().setTitle("Your Quests"); + + List all = getPlayerQuestsSection(player, false); + + for(DialogSection section : all) { + result.addSection(section); + } + + return result; + } + + public static void playerQuestsDialogHandle(Player player, Object[] ans) { + if(ans.length > 0 && "cancel".equals(ans[0])) return; + + if(ans.length == 0 || !(ans[0] instanceof String)) return; + + String[] args = ((String) ans[0]).split("\\."); + + if("quest".equals(args[0])) { + if(args.length >= 3 && "cancel".equals(args[2])) { + PlayerQuests.cancelQuest(player, args[1]); + } + } + } + + public static List getPlayerQuestsSection(Player player, boolean canFinishQuest) { + List result = new ArrayList<>(); + for(QuestProgress questProgress : player.getQuestProgresses().values()) { + if(!questProgress.isComplete()) { + result.addAll(questProgress.getDialogSection(player, canFinishQuest)); + } + } + + return result; + } + + /* DRIVER FUNCTIONS */ + + public static void offerQuests(Player player, List quests, Consumer onSelect) { + player.showDialog(questOffersDialogGet(quests), ans -> { + Quest quest = questOffersDialogGetSelectedOffer(ans); + + if(quest != null) { + offerSingleQuest(player, quest, onSelect); + } + }); + } + + public static void offerSingleQuest(Player player, Quest quest, Consumer onSelect) { + player.showDialog(confirmBeginQuestDialogGet(quest), ans -> { + if(ans.length < 1 || "cancel".equals(ans[0])) return; + onSelect.accept(quest); + }); + } + + public static void showPlayerQuests(Player player) { + player.showDialog(playerQuestsDialogGet(player), ans -> playerQuestsDialogHandle(player, ans)); + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java new file mode 100644 index 00000000..981ea3db --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java @@ -0,0 +1,152 @@ +package brainwine.gameserver.quest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.messages.QuestMessage; +import brainwine.gameserver.util.MapHelper; + +public class PlayerQuests { + private PlayerQuests() {} + + public static void beginQuest(Player player, Quest quest) { + if(player == null) return; + + if(quest == null) { + player.notify("Quest not found!"); + return; + } + + List progresses = new ArrayList<>(quest.getTasks().size()); + + for(int i = 0; i < quest.getTasks().size(); i++) { + progresses.add(0); + } + + QuestProgress progress = new QuestProgress(quest.getId(), progresses); + + player.getQuestProgresses().put(quest.getId(), progress); + + player.notify("Quest has started! Use the /quests command to view your progress at any time."); + sendPlayerQuestMessage(player, progress); + performAction(player, quest, QuestAction.Type.BEGIN); + } + + public static boolean isTaskComplete(Quest quest, Player player, int i) { + int progress = player.getQuestProgresses().get(quest.getId()).getTaskProgress(i); + + QuestTask task = quest.getTasks().get(i); + + boolean satisfiesQuantity = task.getQuantity() <= progress; + boolean satisfiesInventory = task.getCollectInventory() == null || task.getCollectInventory().playerSatisfies(player); + + return satisfiesQuantity && satisfiesInventory; + } + + public static boolean canFinishQuest(Player player, Quest quest) { + if(player == null) return false; + + if(quest == null) { + player.notify("Quest not found!"); + return false; + } + + QuestProgress progress = player.getQuestProgresses().get(quest.getId()); + + if(progress == null) { + player.notify("Your quest progress is not found!"); + return false; + } + + for(int i = 0; i < quest.getTasks().size(); i++) { + if(!isTaskComplete(quest, player, i)) { + return false; + } + } + + return true; + } + + public static void cancelQuest(Player player, String questId) { + QuestProgress progress = player.getQuestProgresses().get(questId); + + if(progress == null || progress.isComplete()) return; + + String reason = progress.tryCancelOtherwiseReason(player); + if(reason != null) { + player.showDialog(DialogHelper.messageDialog("Cannot Cancel Quest", reason)); + return; + } + + player.getQuestProgresses().remove(questId); + sendPlayerCancelQuestMessage(player, questId); + } + + public static void finishQuest(Player player, Quest quest) { + QuestProgress progress = player.getQuestProgresses().get(quest.getId()); + + if(progress.isComplete()) return; + + for(QuestTask task : quest.getTasks()) { + if(task.getCollectInventory() != null) { + task.getCollectInventory().removeFromPlayer(player); + } + } + + for(int i = 0; i < quest.getTasks().size(); i++) { + progress.getTaskProgresses().set(i, quest.getTasks().get(i).getQuantity()); + } + + quest.getReward().reward(player); + + progress.markAsComplete(); + + sendPlayerQuestMessage(player, progress); + // TODO: this should normally happen without the player having to return to the android + performAction(player, quest, QuestAction.Type.DONE); + } + + public static void performAction(Player player, Quest quest, QuestAction.Type actionType) { + if(quest.getActions() == null) return; + + List actions = quest.getActions().get(actionType); + + if(actions == null) return; + + for(QuestAction action : actions) { + action.performAction(player); + } + } + + public static void sendInitialPlayerQuestMessages(Player player) { + Map progresses = player.getQuestProgresses(); + + if(progresses == null) return; + + for(QuestProgress progress : progresses.values()) { + sendPlayerQuestMessage(player, progress); + } + } + + public static void sendPlayerQuestMessage(Player player, QuestProgress progress) { + Quest quest = Quests.get(progress.getQuestId()); + + if(quest == null) return; + + // TODO: detect mobile player properly + if(player.isV3()) { + player.sendMessage(new QuestMessage(quest.getPcDetails(), progress.getClientStatus())); + } else { + player.sendMessage(new QuestMessage(quest.getMobileDetails(), progress.getClientStatus())); + } + + } + + public static void sendPlayerCancelQuestMessage(Player player, String questId) { + player.sendMessage(new QuestMessage(MapHelper.map("id", questId), null)); + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java new file mode 100644 index 00000000..131915bf --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java @@ -0,0 +1,137 @@ +package brainwine.gameserver.quest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import brainwine.gameserver.GameConfiguration; +import brainwine.gameserver.util.MapHelper; + +@JsonIgnoreProperties("zones") +public class Quest { + private String id; + + @JsonProperty("group") + private String group; + + @JsonProperty("title") + private String title; + + @JsonProperty("reward") + private QuestReward reward; + + @JsonProperty("story") + private QuestStory story; + + @JsonProperty("desc") + private String description; + + @JsonProperty(value = "desc_mobile", required = false) + private String descriptionMobile = null; + + @JsonProperty(value = "actions", required = false) + private Map> actions = new HashMap<>(); + + @JsonProperty("tasks") + private List tasks; + + @JsonIgnore + private Map pcDetails = null; + + @JsonIgnore + private Map mobileDetails = null; + + public static Quest get(String questId) { + Quest quest = MapHelper.get(GameConfiguration.getBaseConfig(), questId, Quest.class); + + if(quest == null) return null; + + quest.setId(questId); + return quest; + } + + private void computeClientDetailsIfAbsent() { + if(pcDetails == null || mobileDetails == null) { + pcDetails = new HashMap<>(); + mobileDetails = pcDetails; + + pcDetails.put("id", getId()); + pcDetails.put("group", getGroup()); + pcDetails.put("title", getTitle()); + pcDetails.put("xp", getReward().getXp() == null ? 0 : getReward().getXp()); + pcDetails.put("desc", getDescription()); + + List tasks = new ArrayList<>(); + + if(getTasks() != null) for(QuestTask task : getTasks()) { + tasks.add(task.getDescription()); + } + + pcDetails.put("tasks", tasks); + + if(getDescriptionMobile() != null) { + mobileDetails = new HashMap<>(pcDetails); + + mobileDetails.put("desc", getDescriptionMobile()); + } + } + } + + @JsonIgnore + public Map getPcDetails() { + computeClientDetailsIfAbsent(); + return pcDetails; + } + + @JsonIgnore + public Map getMobileDetails() { + computeClientDetailsIfAbsent(); + return mobileDetails; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getGroup() { + return group; + } + + public String getTitle() { + return title; + } + + public QuestReward getReward() { + return reward; + } + + public QuestStory getStory() { + return story; + } + + public String getDescription() { + return description; + } + + public String getDescriptionMobile() { + return descriptionMobile; + } + + public Map> getActions() { + return actions; + } + + public List getTasks() { + return tasks; + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestAction.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestAction.java new file mode 100644 index 00000000..69d8e115 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestAction.java @@ -0,0 +1,112 @@ +package brainwine.gameserver.quest; + +import java.lang.IllegalArgumentException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; + +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.messages.EventMessage; +import brainwine.gameserver.server.messages.InventoryMessage; +import brainwine.shared.JsonHelper; + +public class QuestAction { + public static enum Type { + INTERACT, + BEGIN, + DONE; + } + + public static enum Actor { + PLAYER, + ANDROID; + } + + private Actor actor = Actor.PLAYER; + + private String method; + + private List params = new ArrayList<>(0); + + @JsonSetter("params") + public void setParams(Object params) { + if(params instanceof List) this.params = (List)params; + else this.params.add(params); + } + + public Actor getActor() { + return actor; + } + + public String getMethod() { + return method; + } + + public List getParams() { + return params; + } + + public void performAction(Player player) { + try{ + switch(getMethod()) { + case "gift_items!": + for(Object object : getParams()) { + Map items = JsonHelper.readValue(object, new TypeReference>() {}); + for(String k : items.keySet()) { + Item item = Item.get(k); + if(item == null) continue; + player.getInventory().addItem(item, items.get(k)); + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + } + } + break; + case "event_message!": + if(params.size() < 2 || !(params.get(0) instanceof String)) { + throw new IllegalArgumentException(); + } + player.sendMessage(new EventMessage((String) params.get(0), params.get(1))); + break; + case "set_family_name!": + break; + case "show_android_dialog": + String body = "No info.", title = null; + if(params.size() >= 1) { + if(!(params.get(0) instanceof String)) throw new IllegalArgumentException(); + body = (String) params.get(0); + } + + if(params.size() >= 2) { + if(!(params.get(0) instanceof String)) throw new IllegalArgumentException(); + title = (String) params.get(0); + } + + if(title == null) { + player.showDialog(DialogHelper.messageDialog(body)); + } else { + player.showDialog(DialogHelper.messageDialog(title, body)); + } + break; + case "add_xp": + if(params.size() >= 1) { + int amount = JsonHelper.readValue(params.get(0), new TypeReference() {}); + player.addExperience(amount); + } + break; + default: + player.notify(String.format("Unknown quest action %d", getMethod())); + } + } catch(JsonProcessingException e) { + player.notify("Couldn't perform some actions for this quest due to JSON processing errors."); + } catch(IllegalArgumentException e) { + player.notify(String.format("Malformed quest action parameters for %d.", getMethod())); + } + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java new file mode 100644 index 00000000..cd9749d5 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java @@ -0,0 +1,142 @@ +package brainwine.gameserver.quest; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +public class QuestEvents { + public static void handleQuestFinalReturn(Player player, Quest quest) { + QuestProgress progress = player.getQuestProgresses().get(quest.getId()); + + if(progress == null) return; + + int found = -1; + + for(int i = 0; i < quest.getTasks().size(); i++) { + QuestTask task = quest.getTasks().get(i); + + if(task.getEvents() != null) { + for(List event : task.getEvents()) { + for(Object o : event) { + if("return".equals(o)) found = i; + if(found != -1) break; + } + if(found != -1) break; + } + } + if(found != -1) break; + } + + if(found == -1) return; + + int initialReturnProgress = progress.getTaskProgress(found); + int wantedProgress = quest.getTasks().get(found).getQuantity(); + + while (progress.getTaskProgresses().size() < quest.getTasks().size()) { + progress.getTaskProgresses().add(0); + } + + progress.getTaskProgresses().set(found, wantedProgress); + + // if can't finish even with the return task done, set the return task progress to the previous value + if(!PlayerQuests.canFinishQuest(player, quest)) { + progress.getTaskProgresses().set(found, initialReturnProgress); + } + } + + private static boolean patternMatch(List event, Object[] pattern) { + if(event.size() != pattern.length) return false; + int iterationCount = Math.min(event.size(), pattern.length); + for(int i = 0; i < iterationCount; i++) { + if(pattern[i] == null) continue; + if(event.get(i) == null) continue; + + if(!Objects.equals(pattern[i], event.get(i))) return false; + } + + return true; + } + + public static void handleEvent(Player player, Object... pattern) { + for(Map.Entry questProgressEntry : player.getQuestProgresses().entrySet()) { + String questId = questProgressEntry.getKey(); + QuestProgress questProgress = questProgressEntry.getValue(); + Quest quest = Quests.get(questId); + int i = 0; + + boolean anyProgress = false; + for(QuestTask task : quest.getTasks()) { + if(task.getEvents() == null) continue; + + if(!task.doesQualify(player)) { + continue; + } + + for(List event : task.getEvents()) { + if(patternMatch(event, pattern)) { + questProgress.getTaskProgresses().set(i, questProgress.getTaskProgress(i) + 1); + + if(event.size() >= 3 && "interact".equals(event.get(0)) && "name".equals(event.get(1))) { + PlayerQuests.performAction(player, quest, QuestAction.Type.INTERACT); + } + anyProgress = true; + break; + } + } + i++; + } + + if(anyProgress) { + PlayerQuests.sendPlayerQuestMessage(player, questProgress); + } + } + } + + public static void handleEnterZone(Player player, Zone zone) { + handleEvent(player, "entered", "zone_name", zone.getName()); + } + + public static void handleKill(Player player, Entity other) { + handleEvent(player, "kill"); + handleEvent(player, "kill", "code", other.getType()); + + if(other.isPlayer()) { + // don't reward players for killing each other + } else { + Npc npc = (Npc) other; + handleEvent(player, "kill", "category", npc.getConfig().getCategory()); + } + + } + + public static void handleExplode(Player player, Entity other) { + handleEvent(player, "explode"); + handleEvent(player, "explode", "code", other.getType()); + + if(other.isPlayer()) { + // don't reward players for killing each other + } else { + Npc npc = (Npc) other; + handleEvent(player, "explode", "category", npc.getConfig().getCategory()); + } + } + + public static void handleChat(Player player) { + handleEvent(player, "chat"); + } + + public static void handleInteract(Player player, Npc npc) { + handleEvent(player, "interact", "name", npc.getName()); + } + + public static void handleAppearance(Player player, Map appearance) { + for(Object code : appearance.values()) { + handleEvent(player, "appearance", "code", code); + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java new file mode 100644 index 00000000..7377bc75 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java @@ -0,0 +1,164 @@ +package brainwine.gameserver.quest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import brainwine.gameserver.dialog.DialogListItem; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.player.Player; + +public class QuestProgress { + @JsonProperty("quest_id") + private String questId; + @JsonProperty("tasks") + @JsonInclude(Include.NON_NULL) + private List taskProgresses; + @JsonProperty("completed_at") + @JsonInclude(Include.NON_NULL) + private Long completedAt = null; + + public QuestProgress() {} + public QuestProgress(String questId, List taskProgresses) { + this.questId = questId; + this.taskProgresses = taskProgresses; + } + + @JsonIgnore + public Quest getQuest() { + return Quests.get(getQuestId()); + } + + public int getTaskProgress(int index) { + if(index < 0 || index >= getTaskProgresses().size()) { + return 0; + } + + return getTaskProgresses().get(index); + } + + public String getQuestId() { + return questId; + } + + public List getTaskProgresses() { + return taskProgresses; + } + + public Long getCompletedAt() { + return completedAt; + } + + public String getActionChoice(String action) { + return String.format("quest.%s.%s", questId, action); + } + + @JsonIgnore + public boolean isComplete() { + return completedAt != null; + } + + public void markAsComplete() { + completedAt = System.currentTimeMillis(); + } + + public List getDialogSection(Player player, boolean canFinishQuest) { + List result = new ArrayList<>(); + DialogSection mainSection = new DialogSection(); + result.add(mainSection); + + Quest quest = getQuest(); + + if(quest == null) { + mainSection.setTitle("QUEST NOT FOUND"); + + return result; + } + + mainSection.setTitle(quest.getTitle()); + + if(PlayerQuests.canFinishQuest(player, quest)) { + mainSection.addItem(new DialogListItem().setImage("shop/premium").setText("All tasks done. Visit a quester android to claim your reward!")); + } + + for(int i = 0; i < quest.getTasks().size(); i++) { + result.add(quest.getTasks().get(i).getDialogSection(getTaskProgress(i))); + } + + if(canFinishQuest) { + result.add(new DialogSection().setText("Finish Quest").setChoice(getActionChoice("finish"))); + } + + DialogSection cancelSection = new DialogSection().setChoice(getActionChoice("cancel")); + + if(player.isV3()) { + cancelSection.setText("Cancel Quest"); + } else { + cancelSection.setText("Cancel Quest").setTextColor("#ff0000"); + } + + result.add(cancelSection); + + return result; + } + + @JsonIgnore + public Map getClientStatus() { + Map result = new HashMap<>(); + Quest quest = Quests.get(getQuestId()); + + List completedIndices = new ArrayList<>(); + + for(int i = 0; i < quest.getTasks().size(); i++) { + QuestTask task = quest.getTasks().get(i); + if(task.getQuantity() <= getTaskProgress(i)) { + completedIndices.add(i); + } + } + + result.put("progress", completedIndices); + result.put("complete", getCompletedAt() != null); + result.put("active", true); + + return result; + } + + /**Revert all the progress, or return a string reason if this will fail. + * + * @param player player to cancel the quest for + * @return null if the cancellation succeeded, or a string if there is a problem + */ + public String tryCancelOtherwiseReason(Player player) { + if(player.isGodMode()) return null; + + Quest quest = getQuest(); + if(quest == null) return null; + + String reason = null; + int count = 0; + for(QuestAction.Type action : new QuestAction.Type[] { QuestAction.Type.BEGIN, QuestAction.Type.INTERACT }) { + if(quest.getActions().containsKey(action)) { + if(reason == null) { + reason = action.toString(); + } else { + reason += ", " + action.toString(); + } + } + + count++; + } + + if(reason == null) { + return null; + } else { + return "Cannot cancel because the quest has the " + reason + (count == 1 ? " action." : " actions."); + } + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java new file mode 100644 index 00000000..c830781a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java @@ -0,0 +1,45 @@ +package brainwine.gameserver.quest; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.loot.Loot; +import brainwine.gameserver.player.Player; + +public class QuestReward { + @JsonProperty(required = false) + private Integer xp = null; + @JsonProperty(required = false) + private Integer crowns = null; + @JsonProperty(value = "loot_categories", required = false) + private List lootCategories; + + public void reward(Player player) { + if(xp != null) { + player.addCrowns(crowns); + } + if(crowns != null) { + player.addExperience(xp, String.format("You have gained %d XP from completing this quest!", xp, crowns)); + } + if(lootCategories != null) { + Loot loot = GameServer.getInstance().getLootManager().getRandomLoot(player, lootCategories); + if(loot != null) { + player.awardLoot(loot); + } + } + } + + public Integer getXp() { + return xp; + } + + public Integer getCrowns() { + return crowns; + } + + public List getLootCategories() { + return lootCategories; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestStory.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestStory.java new file mode 100644 index 00000000..b592cbd1 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestStory.java @@ -0,0 +1,46 @@ +package brainwine.gameserver.quest; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import brainwine.gameserver.player.Player; + +public class QuestStory { + private String intro; + private String accept; + private String begin; + @JsonProperty("begin_mobile") + private String beginMobile; + private String incomplete; + private String complete; + + public String getIntro() { + return intro; + } + public String getAccept() { + return accept; + } + public String getBegin() { + return begin; + } + public String getBeginMobile() { + return beginMobile; + } + public String getIncomplete() { + return incomplete; + } + public String getComplete() { + return complete; + } + public String getBegin(Player player) { + // TODO: check mobile device player properly + if(getBeginMobile() == null) { + return getBegin(); + } + + if(player.isV3()) { + return getBegin(); + } else { + return getBeginMobile(); + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java new file mode 100644 index 00000000..619871b2 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java @@ -0,0 +1,123 @@ +package brainwine.gameserver.quest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import brainwine.gameserver.dialog.DialogListItem; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.player.Player; + +public class QuestTask { + @JsonProperty("desc") + private String description; + + @JsonProperty("quantity") + private int quantity = 1; + + @JsonProperty("events") + private List> events = new ArrayList<>(); + + @JsonProperty("progress") + private List> progress = new ArrayList<>(); + + @JsonProperty("qualify") + private List> qualify = new ArrayList<>(); + + @JsonProperty("action") + private String action; + + @JsonProperty("collect_inventory") + private QuestTaskCollectInventory collectInventory; + + public boolean checkQualification(Player player, Object... qualification) { + if(player == null || qualification == null || qualification.length == 0) { + return false; + } + + if(Objects.equals("pvp?", qualification[0])) { + return true; // TODO: change when PVP is supported. + } + + if(Objects.equals("current_biome?", qualification[0])) { + return qualification.length >= 2 && Objects.equals(qualification[1], player.getZone().getBiome().getId()); + } + + if(Objects.equals("current_item_group?", qualification[0])) { + return qualification.length >= 2 && Objects.equals(qualification[1], player.getHeldItem().getId()); + } + + return true; + } + + public boolean doesQualify(Player player) { + if(this.getQualify() == null) return true; + + for(List qualification : this.getQualify()) { + if(!checkQualification(player, qualification)) return false; + } + + return true; + } + + public String getDescription() { + return description; + } + + public int getQuantity() { + return quantity; + } + + public List> getEvents() { + return events; + } + + public List> getProgress() { + return progress; + } + + public List> getQualify() { + return qualify; + } + + public String getAction() { + return action; + } + + public QuestTaskCollectInventory getCollectInventory() { + return collectInventory; + } + + public DialogSection getDialogSection(int taskProgress) { + DialogSection result = new DialogSection(); + + result.setText(getDescription() + (taskProgress >= 0 ? String.format("(Progress: %d/%d)", taskProgress, getQuantity()) : "")); + + if(!getQualify().isEmpty()) { + result.addItem(new DialogListItem().setText("Qualifications:")); + for (List qualification : getQualify()) { + result.addItem(new DialogListItem().setText(qualification.stream().map(Objects::toString).collect(Collectors.joining(" ")))); + } + } + + if(!getEvents().isEmpty()) { + result.addItem(new DialogListItem().setText("Do any of these to make progress:")); + for (List event : getEvents()) { + result.addItem(new DialogListItem().setText(event.stream().map(Objects::toString).collect(Collectors.joining(" ")))); + } + } + + if(getCollectInventory() != null && !getCollectInventory().getRequirements().isEmpty()) { + result.addItem(new DialogListItem().setText("Things To Collect:")); + + getCollectInventory().addDialogListItems(result); + } + + return result; + + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java new file mode 100644 index 00000000..527eb8bf --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java @@ -0,0 +1,63 @@ +package brainwine.gameserver.quest; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import brainwine.gameserver.dialog.DialogListItem; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.item.LazyItemGetter; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.util.Pair; + +public class QuestTaskCollectInventory { + List> requirements; + + @JsonCreator + private QuestTaskCollectInventory(Map inp) { + requirements = inp.entrySet().stream() + .map(e -> new Pair<>(new LazyItemGetter(e.getKey()), e.getValue())) + .collect(Collectors.toList()); + } + + public List> getRequirements() { + return requirements; + } + + public boolean playerSatisfies(Player player) { + if(player == null) { + return false; + } + + for(Pair req : requirements) { + if(!player.getInventory().hasItem(req.getFirst().get(), req.getLast())) { + return false; + } + } + + return true; + + } + + public void removeFromPlayer(Player player) { + if(player == null) { + return; + } + + for(Pair req : requirements) { + player.getInventory().removeItem(req.getFirst().get(), req.getLast()); + } + } + + public void addDialogListItems(DialogSection section) { + for(Pair req : getRequirements()) { + section.addItem( + new DialogListItem() + .setItem(req.getFirst().get().getCode())) + .setText(req.getFirst().get().getTitle()); + } + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java new file mode 100644 index 00000000..d0a41ab9 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java @@ -0,0 +1,147 @@ +package brainwine.gameserver.quest; + +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.core.type.TypeReference; + +import brainwine.gameserver.GameConfiguration; +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.resource.ResourceFinder; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.util.PickRandom; +import brainwine.shared.JsonHelper; + +public class Quests { + public static Map> questMaps = new HashMap<>(); + public static Map> questLists = new HashMap<>(); + public static Map titleToPrefix = new HashMap<>(); + public static Map hardcodedQuests = new HashMap<>(); + + private static final Logger logger = LogManager.getLogger(); + + private Quests() {} + + private static void loadHardcodedQuests() { + try { + URL url = ResourceFinder.getResourceUrl("hardcoded-quests.json"); + Map> obj = JsonHelper.readValue(url, new TypeReference>>(){}); + hardcodedQuests.putAll(MapHelper.getMap(obj, "hardcoded_quests")); + } catch (Exception e) { + logger.warn(String.format("Could not load any hardcoded quest entries from hardcoded-quests.json: %s", e.getMessage())); + } + } + + private static int loadQuestsForCategory(String categoryPrefix, String categoryTitle) { + titleToPrefix.put(categoryTitle, categoryPrefix); + + Map config = GameConfiguration.getBaseConfig(); + Map resultMap = new HashMap<>(); + List resultList = new ArrayList<>(); + + int counter = 0; + for(String questId : config.keySet()) { + try { + if(questId.startsWith(categoryPrefix)) { + Quest quest = JsonHelper.readValue(MapHelper.getMap(config, questId), Quest.class); + quest.setId(questId); + resultMap.put(questId, quest); + resultList.add(quest); + counter++; + } + } catch (Exception e) { + logger.warn(String.format("Could not load quest %s: %s", questId, e.getMessage())); + } + } + + questMaps.put(categoryTitle, resultMap); + questLists.put(categoryTitle, resultList); + + return counter; + } + + public static void loadQuests() { + loadHardcodedQuests(); + + int counter = 0; + + counter += loadQuestsForCategory("collect", "Arts and Crafts"); + counter += loadQuestsForCategory("combat", "The Art of War"); + counter += loadQuestsForCategory("cooking", "Let Them Eat Cake"); + counter += loadQuestsForCategory("survival", "Survive and Thrive"); + + logger.info("Successfully loaded {} quests", counter); + } + + public static Quest get(String questId) { + Map targetMap = null; + + for(String key : titleToPrefix.keySet()) { + if(questId.startsWith(titleToPrefix.get(key))) targetMap = questMaps.get(key); + } + + if(targetMap == null) { + return null; + } + + Quest quest = targetMap.get(questId); + + if(quest == null) { + return null; + } + + quest.setId(questId); + + return quest; + } + + public static List getRandomQuestsFromCategory(Entity me, String categoryTitle, Set excludeQuestIds, int count) { + List targetList = questLists.get(categoryTitle); + targetList = targetList.stream().filter(q -> !excludeQuestIds.contains(q.getId())).collect(Collectors.toList()); + + if(targetList == null) { + return null; + } + + Random random = new Random(me == null ? 123456789L : me.hashCode()); + + return PickRandom.sampleWithoutReplacement(random, targetList, count); + } + + public static QuestProgress getIncompleteQuestProgressInCategory(Player player, String categoryTitle) { + String prefix = titleToPrefix.get(categoryTitle); + + if(prefix == null) { + return null; + } + + if(player.getQuestProgresses() == null) { + return null; + } + + String id = player.getQuestProgresses().keySet().stream() + .filter(p -> !player.getQuestProgresses().get(p).isComplete() && p.startsWith(prefix)) + .findFirst().orElse(null); + + return player.getQuestProgresses().get(id); + } + + /** + * Populate hardcoded-quests.json with Android names for which a specific quest will + * be issued to the player instead of showing the quest offers menu. + */ + public static Map getHardcodedQuests() { + return hardcodedQuests; + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/QuestMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/QuestMessage.java new file mode 100644 index 00000000..79ef0862 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/QuestMessage.java @@ -0,0 +1,18 @@ +package brainwine.gameserver.server.messages; + +import java.util.Map; + +import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; + + +@MessageInfo(id = 63, collection = true) +public class QuestMessage extends Message { + public Map details; + public Map status; + + public QuestMessage(Map clientDetails, Map clientStatus) { + details = clientDetails; + status = clientStatus; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/PickRandom.java b/gameserver/src/main/java/brainwine/gameserver/util/PickRandom.java new file mode 100644 index 00000000..354bdaa5 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/PickRandom.java @@ -0,0 +1,55 @@ +package brainwine.gameserver.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class PickRandom { + + /**Draw sampleSize random items from the given array. + * + * @param type of elements + * @param arr the array + * @param sampleSize the number of items to return + * @returns a new array + */ + public static List sampleWithoutReplacement(List arr, int sampleSize) { + return sampleWithoutReplacement(ThreadLocalRandom.current(), arr, sampleSize); + } + + /**Draw sampleSize random items from the given array and the random number generator. + * + * @param type of elements + * @param random Random instance to roll dice with + * @param arr the array + * @param sampleSize the number of items to return + * @returns a new array + */ + public static List sampleWithoutReplacement(Random random, List arr, int sampleSize) { + if(arr.size() <= sampleSize) { + return new ArrayList<>(arr); + } + + int selected = 0; + int dealtWith = 0; + + List result = new ArrayList<>(sampleSize); + + while(selected < sampleSize) + { + double uniform = random.nextDouble(); + + if(uniform * (arr.size() - dealtWith) >= sampleSize - selected) { + dealtWith++; + } else { + result.add(arr.get(dealtWith)); + selected++; + dealtWith++; + } + } + + return result; + } + +} From 71b3054ca6d7fb1401c5436e3c6526389e6f5bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 22 Oct 2024 00:40:59 +0200 Subject: [PATCH 02/36] wire up quests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Bora İnevi --- .../java/brainwine/gameserver/GameServer.java | 2 ++ .../gameserver/command/QuestsCommand.java | 29 +++++++++++++++++++ .../brainwine/gameserver/entity/Entity.java | 15 ++++++++++ .../gameserver/zone/EntityManager.java | 2 ++ .../java/brainwine/gameserver/zone/Zone.java | 6 ++++ .../src/main/resources/hardcoded-quests.json | 7 +++++ 6 files changed, 61 insertions(+) create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/QuestsCommand.java create mode 100644 gameserver/src/main/resources/hardcoded-quests.json diff --git a/gameserver/src/main/java/brainwine/gameserver/GameServer.java b/gameserver/src/main/java/brainwine/gameserver/GameServer.java index ebb8f253..eb1f0ec8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameServer.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameServer.java @@ -16,6 +16,7 @@ import brainwine.gameserver.player.NotificationType; import brainwine.gameserver.player.PlayerManager; import brainwine.gameserver.prefab.PrefabManager; +import brainwine.gameserver.quest.Quests; import brainwine.gameserver.server.NetworkRegistry; import brainwine.gameserver.server.Server; import brainwine.gameserver.zone.EntityManager; @@ -50,6 +51,7 @@ public GameServer() { EntityRegistry.init(); EntityManager.loadEntitySpawns(); GrowthManager.loadGrowthData(); + Quests.loadQuests(); lootManager = new LootManager(); prefabManager = new PrefabManager(); ZoneGenerator.init(); diff --git a/gameserver/src/main/java/brainwine/gameserver/command/QuestsCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/QuestsCommand.java new file mode 100644 index 00000000..28a9e92b --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/QuestsCommand.java @@ -0,0 +1,29 @@ +package brainwine.gameserver.command; + +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.quest.PlayerQuestDialog; + +@CommandInfo(name = "quests", description = "Lists your ongoing and completed quests.") +public class QuestsCommand extends Command { + + @Override + public void execute(CommandExecutor executor, String[] args) { + if (!(executor instanceof Player)) { + executor.notify("You can only view your quests as a player!", NotificationType.SYSTEM); + } + + PlayerQuestDialog.showPlayerQuests((Player) executor); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/quests"; + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor instanceof Player; + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index 8bb3e79c..f96a9230 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -11,6 +11,7 @@ import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.player.Player; +import brainwine.gameserver.quest.QuestEvents; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.messages.EffectMessage; import brainwine.gameserver.server.messages.EntityChangeMessage; @@ -101,12 +102,22 @@ public void attack(Entity attacker, Item weapon, float baseDamage, DamageType da // Kill entity if attacker is a player in god mode if(attacker != null && attacker.isPlayer() && ((Player)attacker).isGodMode()) { setHealth(0.0F); + + if(isDead()) { + QuestEvents.handleKill((Player) attacker, this); + } + return; } // Ignore multipliers if true damage should be dealt if(trueDamage) { setHealth(health - baseDamage); + + if(isDead() && attacker != null && attacker.isPlayer()) { + QuestEvents.handleKill((Player) attacker, this); + } + return; } @@ -114,6 +125,10 @@ public void attack(Entity attacker, Item weapon, float baseDamage, DamageType da float defense = Math.max(0.0F, 1.0F - getDefense(attack)); float damage = baseDamage * attackMultiplier * defense; setHealth(health - damage); + + if(isDead() && attacker != null && attacker.isPlayer()) { + QuestEvents.handleKill((Player) attacker, this); + } } public float getAttackMultiplier(EntityAttack attack) { diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 51b53fca..65d510de 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -30,6 +30,7 @@ import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.ModType; import brainwine.gameserver.player.Player; +import brainwine.gameserver.quest.PlayerQuests; import brainwine.gameserver.resource.ResourceFinder; import brainwine.gameserver.server.messages.EntityPositionMessage; import brainwine.gameserver.server.messages.EntityStatusMessage; @@ -296,6 +297,7 @@ public void addEntity(Entity entity) { player.onZoneChanged(); players.put(entityId, player); playersByName.put(player.getName().toLowerCase(), player); + PlayerQuests.sendInitialPlayerQuestMessages(player); player.sendMessageToPeers(new EntityStatusMessage(player, EntityStatus.ENTERING)); player.sendMessageToPeers(new EntityPositionMessage(player)); } else if(entity instanceof Npc) { diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 86a4e422..f395592e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -38,6 +38,7 @@ import brainwine.gameserver.player.NotificationType; import brainwine.gameserver.player.Player; import brainwine.gameserver.prefab.Prefab; +import brainwine.gameserver.quest.QuestEvents; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.messages.BlockChangeMessage; import brainwine.gameserver.server.messages.BlockMetaMessage; @@ -276,6 +277,7 @@ public void sendChatMessage(Player sender, String text) { */ public void sendChatMessage(Player sender, String text, ChatType type) { sendMessage(new ChatMessage(sender.getId(), text, type)); + QuestEvents.handleChat(sender); GameServer.getInstance().notify(String.format("%s: %s", sender.getName(), text), NotificationType.CHAT); } @@ -488,6 +490,10 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv double distance = MathUtils.distance(x, y, entity.getX(), entity.getY()); float damage = (float)(baseDamage - distance); entity.attack(cause, item, damage, damageType); + + if(entity.isDead() && cause.isPlayer()) { + QuestEvents.handleExplode((Player) cause, entity); + } } } } diff --git a/gameserver/src/main/resources/hardcoded-quests.json b/gameserver/src/main/resources/hardcoded-quests.json new file mode 100644 index 00000000..5ca09191 --- /dev/null +++ b/gameserver/src/main/resources/hardcoded-quests.json @@ -0,0 +1,7 @@ +{ + "hardcoded_quests": { + "Say Hello": { + "quest": "survival_say_hello" + } + } +} \ No newline at end of file From 3e68e688c891dfafae70ea6b6b76581f0ef9fb37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Fri, 25 Oct 2024 00:06:50 +0200 Subject: [PATCH 03/36] add random quest generation --- .../gameserver/entity/EntityConfig.java | 5 + .../brainwine/gameserver/quest/Quest.java | 59 +++++++- .../gameserver/quest/QuestReward.java | 15 ++ .../brainwine/gameserver/quest/QuestTask.java | 39 ++++- .../quest/QuestTaskCollectInventory.java | 8 +- .../brainwine/gameserver/quest/Quests.java | 6 + .../gameserver/quest/RandomQuest.java | 44 ++++++ .../gameserver/quest/RandomQuestReward.java | 29 ++++ .../gameserver/quest/RandomQuests.java | 89 ++++++++++++ .../quest/randomquests/Collect.java | 68 +++++++++ .../gameserver/quest/randomquests/Kill.java | 133 ++++++++++++++++++ .../util/randomobject/Arbitrary.java | 13 ++ .../util/randomobject/ConcatList.java | 47 +++++++ .../ConcretionFailureException.java | 8 ++ .../util/randomobject/ConstantList.java | 30 ++++ .../util/randomobject/RandomInteger.java | 52 +++++++ .../util/randomobject/RandomList.java | 60 ++++++++ .../util/randomobject/RandomPickList.java | 47 +++++++ 18 files changed, 744 insertions(+), 8 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/Arbitrary.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcretionFailureException.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java index 4ee75842..6848fd92 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java @@ -27,6 +27,7 @@ public class EntityConfig { private final String name; private final int type; + private String title = "Unknown"; private int experienceYield; private float maxHealth = Entity.DEFAULT_HEALTH; private float baseSpeed = 3; @@ -70,6 +71,10 @@ public int getType() { return type; } + public String getTitle() { + return title; + } + @JsonIgnore public String getCategory() { String id = getName(); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java index 131915bf..e546aa78 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java @@ -98,10 +98,6 @@ public String getId() { return id; } - public void setId(String id) { - this.id = id; - } - public String getGroup() { return group; } @@ -133,5 +129,60 @@ public Map> getActions() { public List getTasks() { return tasks; } + + public Quest setId(String id) { + this.id = id; + return this; + } + + public Quest setGroup(String group) { + this.group = group; + return this; + } + + public Quest setTitle(String title) { + this.title = title; + return this; + } + + public Quest setReward(QuestReward reward) { + this.reward = reward; + return this; + } + + public Quest setStory(QuestStory story) { + this.story = story; + return this; + } + + public Quest setDescription(String description) { + this.description = description; + return this; + } + + public Quest setDescriptionMobile(String descriptionMobile) { + this.descriptionMobile = descriptionMobile; + return this; + } + + public Quest setActions(Map> actions) { + this.actions = actions; + return this; + } + + public Quest setTasks(List tasks) { + this.tasks = tasks; + return this; + } + + public Quest setPcDetails(Map pcDetails) { + this.pcDetails = pcDetails; + return this; + } + + public Quest setMobileDetails(Map mobileDetails) { + this.mobileDetails = mobileDetails; + return this; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java index c830781a..baff0159 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java @@ -42,4 +42,19 @@ public Integer getCrowns() { public List getLootCategories() { return lootCategories; } + + public QuestReward setXp(Integer xp) { + this.xp = xp; + return this; + } + + public QuestReward setCrowns(Integer crowns) { + this.crowns = crowns; + return this; + } + + public QuestReward setLootCategories(List lootCategories) { + this.lootCategories = lootCategories; + return this; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java index 619871b2..2fa1d8d7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java @@ -67,30 +67,65 @@ public String getDescription() { return description; } + public QuestTask setDescription(String description) { + this.description = description; + return this; + } + public int getQuantity() { return quantity; } + public QuestTask setQuantity(Integer quantity) { + this.quantity = quantity; + return this; + } + public List> getEvents() { return events; } + public QuestTask setEvents(List> events) { + this.events = events; + return this; + } + public List> getProgress() { return progress; } + public QuestTask setProgress(List> progress) { + this.progress = progress; + return this; + } + public List> getQualify() { return qualify; } + public QuestTask setQualify(List> qualify) { + this.qualify = qualify; + return this; + } + public String getAction() { return action; } + public QuestTask setAction(String action) { + this.action = action; + return this; + } + public QuestTaskCollectInventory getCollectInventory() { return collectInventory; } + public QuestTask setCollectInventory(QuestTaskCollectInventory collectInventory) { + this.collectInventory = collectInventory; + return this; + } + public DialogSection getDialogSection(int taskProgress) { DialogSection result = new DialogSection(); @@ -98,14 +133,14 @@ public DialogSection getDialogSection(int taskProgress) { if(!getQualify().isEmpty()) { result.addItem(new DialogListItem().setText("Qualifications:")); - for (List qualification : getQualify()) { + for(List qualification : getQualify()) { result.addItem(new DialogListItem().setText(qualification.stream().map(Objects::toString).collect(Collectors.joining(" ")))); } } if(!getEvents().isEmpty()) { result.addItem(new DialogListItem().setText("Do any of these to make progress:")); - for (List event : getEvents()) { + for(List event : getEvents()) { result.addItem(new DialogListItem().setText(event.stream().map(Objects::toString).collect(Collectors.joining(" ")))); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java index 527eb8bf..d3a5aed7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java @@ -14,9 +14,8 @@ public class QuestTaskCollectInventory { List> requirements; - @JsonCreator - private QuestTaskCollectInventory(Map inp) { + public QuestTaskCollectInventory(Map inp) { requirements = inp.entrySet().stream() .map(e -> new Pair<>(new LazyItemGetter(e.getKey()), e.getValue())) .collect(Collectors.toList()); @@ -26,6 +25,11 @@ public List> getRequirements() { return requirements; } + public QuestTaskCollectInventory setRequirements(List> requirements) { + this.requirements = requirements; + return this; + } + public boolean playerSatisfies(Player player) { if(player == null) { return false; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java index d0a41ab9..8f87152f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java @@ -21,6 +21,7 @@ import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.PickRandom; import brainwine.shared.JsonHelper; +import io.netty.util.internal.ThreadLocalRandom; public class Quests { public static Map> questMaps = new HashMap<>(); @@ -42,6 +43,10 @@ private static void loadHardcodedQuests() { } } + private static void loadRandomQuests() { + RandomQuests.loadConfiguration(); + } + private static int loadQuestsForCategory(String categoryPrefix, String categoryTitle) { titleToPrefix.put(categoryTitle, categoryPrefix); @@ -72,6 +77,7 @@ private static int loadQuestsForCategory(String categoryPrefix, String categoryT public static void loadQuests() { loadHardcodedQuests(); + loadRandomQuests(); int counter = 0; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java new file mode 100644 index 00000000..5cc06345 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java @@ -0,0 +1,44 @@ +package brainwine.gameserver.quest; + +import java.util.Random; + +import brainwine.gameserver.player.Player; +import brainwine.gameserver.quest.randomquests.*; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.netty.util.internal.ThreadLocalRandom; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type", + visible = true +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = Kill.class, name = "kill"), + @JsonSubTypes.Type(value = Collect.class, name = "collect"), +}) +public abstract class RandomQuest { + private String type = "none"; + private int tier = 1; + private int frequency = 1; + + public void nextQuest(Player player, Quest quest) { + nextQuest(ThreadLocalRandom.current(), player, quest); + } + + public abstract void nextQuest(Random random, Player player, Quest quest); + + public String getType() { + return type; + } + + public int getTier() { + return tier; + } + + public int getFrequency() { + return frequency; + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java new file mode 100644 index 00000000..cab1541a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java @@ -0,0 +1,29 @@ +package brainwine.gameserver.quest; + +import java.util.Random; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import brainwine.gameserver.util.randomobject.Arbitrary; +import brainwine.gameserver.util.randomobject.ConcretionFailureException; +import brainwine.gameserver.util.randomobject.RandomInteger; +import brainwine.gameserver.util.randomobject.RandomList; + +public class RandomQuestReward implements Arbitrary { + @JsonProperty("xp") + RandomInteger xp = null; + @JsonProperty("crowns") + RandomInteger crowns = null; + @JsonProperty("loot_categories") + RandomList lootCategories = null; + + @Override + public QuestReward next(Random random) throws ConcretionFailureException { + QuestReward result = new QuestReward(); + if (xp != null) result.setXp(xp.next(random)); + if (crowns != null) result.setCrowns(crowns.next(random)); + if (lootCategories != null) result.setLootCategories(lootCategories.toConcrete(random)); + + return result; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java new file mode 100644 index 00000000..11d85c19 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java @@ -0,0 +1,89 @@ +package brainwine.gameserver.quest; + +import static brainwine.shared.LogMarkers.SERVER_MARKER; + +import java.io.IOException; +import java.net.URL; +import java.util.*; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.core.type.TypeReference; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.resource.ResourceFinder; +import brainwine.gameserver.util.WeightedMap; +import brainwine.shared.JsonHelper; +import io.netty.util.internal.ThreadLocalRandom; + +public class RandomQuests { + private static final Logger logger = LogManager.getLogger(); + private static Configuration configuration = new Configuration(); + + private static class Configuration { + @JsonProperty("level_max_tiers") + private Map levelMaxTiers = new HashMap<>(); + @JsonProperty("strings") + Map> strings = new HashMap<>(); + @JsonProperty("quests") + List randomQuests = new ArrayList<>(); + } + + public static void loadConfiguration() { + logger.info(SERVER_MARKER, "Loading loot tables ..."); + + try { + URL url = ResourceFinder.getResourceUrl("random-quests.json"); + configuration = JsonHelper.readValue(url, new TypeReference(){}); + } catch (IOException e) { + logger.error(SERVER_MARKER, "Failed to load random quests", e); + } + } + + public static int getMaxTier(Player player) { + if(player == null) return 1; + + int tier = 1; + int level = player.getLevel(); + for (Map.Entry e : configuration.levelMaxTiers.entrySet()) { + if (level >= e.getValue() && tier < e.getKey()) { + tier = e.getKey(); + } + } + + return tier; + } + + public static Quest generateRandomPlayerQuest(Player player) { + return generateRandomPlayerQuest(ThreadLocalRandom.current(), player); + } + + public static Quest generateRandomPlayerQuest(Random random, Player player) { + int maxTier = getMaxTier(player); + List candidates = configuration.randomQuests.stream() + .filter(q -> q.getTier() <= maxTier) + .collect(Collectors.toList()); + + WeightedMap wm = new WeightedMap<>( + candidates, + RandomQuest::getFrequency + ); + + RandomQuest choice = wm.next(random); + + Quest quest = new Quest(); + quest.setId("daily_player_quest"); + choice.nextQuest(random, player, quest); + + return quest; + } + + public static String getString(Random random, String label) { + List list = configuration.strings.get(label); + + return list.get(random.nextInt(0, list.size())); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java new file mode 100644 index 00000000..fd29ce79 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -0,0 +1,68 @@ +package brainwine.gameserver.quest.randomquests; + +import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.quest.*; +import brainwine.gameserver.util.randomobject.*; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.*; +import java.util.stream.Collectors; + +public class Collect extends RandomQuest { + @JsonProperty("items") + private RandomList items; + @JsonProperty("reward") + private RandomQuestReward reward = null; + @JsonProperty("task_description") + private String taskDescription = null; + + private class CollectItem implements Arbitrary { + RandomList item = new ConstantList<>(); + RandomInteger count = new RandomInteger(1); + + @Override + public QuestTask next(Random random) throws ConcretionFailureException { + Map requirements = new HashMap<>(); + + for (String oneItem : item.toConcrete(random)) { + requirements.put(oneItem, count.next(random)); + } + + String myTaskDescription; + if (taskDescription == null) { + myTaskDescription = "Collect" + requirements.entrySet().stream().map(e -> { + Item item = Item.get(e.getKey()); + String itemTitle; + if (item == null) { + itemTitle = e.getKey(); + } else { + itemTitle = item.getTitle(); + } + return e.getValue() + " x " + itemTitle; + }).collect(Collectors.joining(", ")); + } else { + myTaskDescription = taskDescription; + } + + return new QuestTask() + .setDescription(myTaskDescription) + .setCollectInventory(new QuestTaskCollectInventory(requirements)); + } + } + + @Override + public void nextQuest(Random random, Player player, Quest quest) { + try { + List collectItemList = items.toConcrete(random); + List tasks = new ArrayList<>(collectItemList.size()); + for(CollectItem c : collectItemList) { + tasks.add(c.next(random)); + } + quest.setTasks(tasks); + if(reward != null) quest.setReward(reward.next(random)); + } catch (ConcretionFailureException e) { + throw new IllegalStateException("Concretion failure!"); + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java new file mode 100644 index 00000000..6ca5e1c3 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -0,0 +1,133 @@ +package brainwine.gameserver.quest.randomquests; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +import brainwine.gameserver.player.Player; +import brainwine.gameserver.quest.*; +import brainwine.gameserver.util.randomobject.ConcretionFailureException; +import brainwine.gameserver.util.randomobject.ConstantList; +import brainwine.gameserver.util.randomobject.RandomInteger; +import brainwine.gameserver.util.randomobject.RandomList; +import com.fasterxml.jackson.annotation.JsonProperty; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; +import org.apache.commons.text.WordUtils; + +public class Kill extends RandomQuest { + @JsonProperty("categories") + private RandomList categories = null; + @JsonProperty("codes") + private RandomList codes = null; + @JsonProperty("entities") + private RandomList entityIds = null; + @JsonProperty("quantity") + private RandomInteger quantity = null; + @JsonProperty("category_quantity") + private RandomInteger categoryQuantity = new RandomInteger(1); + @JsonProperty("code_quantity") + private RandomInteger codeQuantity = new RandomInteger(1); + @JsonProperty("entity_quantity") + private RandomInteger entityIdQuantity = new RandomInteger(1); + @JsonProperty("task_description") + private String taskDescription = null; + @JsonProperty("reward") + private RandomQuestReward reward = null; + @JsonProperty("actions") + private RandomList actions = new ConstantList<>(Arrays.asList("kill")); + + private List> getKillEvents(List actions, String type, List values) { + List> events = new ArrayList<>(actions.size() * values.size()); + for (String action : actions) { + for (Object value : values) { + events.add(Arrays.asList(action, type, value)); + } + } + + return events; + } + + public void setTaskDescription(QuestTask task, String taskDescription, String actionMessage) { + if(taskDescription == null) { + List> events = task.getEvents(); + List values = events.stream().map(i -> i.get(2)).collect(Collectors.toList()); + int quantity = task.getQuantity(); + String times = quantity == 1 ? "" : quantity + " times"; + + String concat; + if(!events.isEmpty() && events.get(0).size() >= 2 && "code".equals(events.get(0).get(1))) { + concat = String.join(", ", values.stream().map(v -> "(Type: " + v + ")").collect(Collectors.toList())); + } else { + concat = String.join(", ", values.stream().map(v -> (String) v).collect(Collectors.toList())); + } + + String message; + if (values.size() == 1) { + message = actionMessage + " an entity " + concat + times; + } else { + message = actionMessage + " any of the entities " + concat + times; + } + + task.setDescription(message); + } else { + task.setDescription(taskDescription.replaceAll("\\{QUANTITY\\}", Integer.toString(task.getQuantity()))); + } + } + + @Override + public void nextQuest(Random random, Player player, Quest quest) { + try { + quest.setTitle("Daily Quest"); + quest.setDescription(RandomQuests.getString(random, "kill_description")); + + List tasks = new ArrayList<>(); + List actions = this.actions.toConcrete(random); + + String actionMessage = WordUtils.capitalize(String.join(" or ", actions)); + + if (categories != null) { + List values = categories.toConcrete(random); + int quantity = defaultIfNull(this.quantity, categoryQuantity).next(random); + QuestTask task = new QuestTask() + .setEvents(getKillEvents(actions, "category", values)) + .setQuantity(quantity); + setTaskDescription(task, taskDescription, actionMessage); + tasks.add(task); + } + + if (entityIds != null) { + List values = entityIds.toConcrete(random); + int quantity = defaultIfNull(this.quantity, entityIdQuantity).next(random); + QuestTask task = new QuestTask() + .setEvents(getKillEvents(actions, "entity", values)) + .setQuantity(quantity); + setTaskDescription(task, taskDescription, actionMessage); + tasks.add(task); + } + + if (codes != null) { + List values = codes.toConcrete(random).stream() + .map(i -> i.next(random)) + .collect(Collectors.toList()); + int quantity = defaultIfNull(this.quantity, codeQuantity).next(random); + QuestTask task = new QuestTask() + .setEvents(getKillEvents(actions, "code", values)) + .setQuantity(quantity); + setTaskDescription(task, taskDescription, actionMessage); + tasks.add(task); + } + + quest.setTasks(tasks); + if (reward != null) quest.setReward(reward.next(random)); + } catch (ConcretionFailureException e) { + throw new IllegalStateException("Concretion failure!"); + } + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/Arbitrary.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/Arbitrary.java new file mode 100644 index 00000000..900237b5 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/Arbitrary.java @@ -0,0 +1,13 @@ +package brainwine.gameserver.util.randomobject; + +import java.util.Random; + +import io.netty.util.internal.ThreadLocalRandom; + +public interface Arbitrary { + T next(Random random) throws ConcretionFailureException; + + default T next() throws ConcretionFailureException { + return next(ThreadLocalRandom.current()); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java new file mode 100644 index 00000000..a2039fcb --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java @@ -0,0 +1,47 @@ +package brainwine.gameserver.util.randomobject; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; + +public class ConcatList extends RandomList { + List> lists; + + public ConcatList() {} + public ConcatList(List> lists) { + this.lists = lists; + } + + public static ConcatList create(Map inp) throws JsonProcessingException { + Object inpList = inp.get("concat"); + + if (inpList instanceof List) { + List> concat = new ArrayList<>(); + + for (Object o : (List)inpList) { + concat.add(RandomList.create(o)); + } + + return new ConcatList<>(concat); + } + + throw new JsonMappingException("Concat list doesn't have a list of lists."); + } + + @Override + public ConstantList next(Random random) throws ConcretionFailureException { + if (lists == null) return null; + + List result = new ArrayList(); + for (RandomList l : lists) { + result.addAll(l.next(random).getList()); + } + + return new ConstantList(result); + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcretionFailureException.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcretionFailureException.java new file mode 100644 index 00000000..8a19ab9f --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcretionFailureException.java @@ -0,0 +1,8 @@ +package brainwine.gameserver.util.randomobject; + +public class ConcretionFailureException extends Exception { + public static final long serialVersionUID = 4235367787L; + + public ConcretionFailureException() { super(); } + public ConcretionFailureException(String message) { super(message); } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java new file mode 100644 index 00000000..931b4eac --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java @@ -0,0 +1,30 @@ +package brainwine.gameserver.util.randomobject; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class ConstantList extends RandomList { + List list; + + public ConstantList() { + this.list = new ArrayList<>(); + } + + public ConstantList(List list) { + this.list = list; + } + + @Override + public ConstantList next(Random random) { + return this; + } + + public int size() { + return list.size(); + } + + public List getList() { + return list; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java new file mode 100644 index 00000000..151012f0 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java @@ -0,0 +1,52 @@ +package brainwine.gameserver.util.randomobject; + +import java.util.List; +import java.util.Random; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonMappingException; + +public class RandomInteger implements Arbitrary { + private boolean isConcrete = false; + private Integer value = null; + private List randomBetween = null; + + public RandomInteger() {} + + @JsonCreator + public RandomInteger(Integer value) { + this.value = value; + this.isConcrete = true; + } + + @JsonCreator + public RandomInteger(@JsonProperty("random_between") List randomBetween ) throws JsonMappingException { + if (!(randomBetween.size() == 2 || randomBetween.size() == 3)) { + throw new JsonMappingException("Invalid input for random_between: provide either 2 or 3 constant numbers"); + } + + if (randomBetween.get(0) > randomBetween.get(1)) { + throw new JsonMappingException("Invalid input for random_between: range min is greater than range max"); + } + + if (randomBetween.size() == 3 && randomBetween.get(2) <= 0) { + throw new JsonMappingException("Invalid input for random_between: only positive step values are allowed"); + } + this.randomBetween = randomBetween; + } + + @Override + public Integer next(Random random) { + if (this.isConcrete) { + return value; + } else { + int step = 1; + if(randomBetween.size() == 3) { + step = randomBetween.get(2); + } + return step * random.nextInt(randomBetween.get(0) / step, randomBetween.get(1) / step + 1); + } + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java new file mode 100644 index 00000000..341ab7a0 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java @@ -0,0 +1,60 @@ +package brainwine.gameserver.util.randomobject; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonMappingException; +import brainwine.shared.JsonHelper; + +public abstract class RandomList implements Arbitrary> { + @JsonCreator + public static RandomList create(List list) throws JsonProcessingException { + ConstantList result = new ConstantList(); + + for (Object o : list) { + result.getList().add(result.createItem(o)); + } + + return result; + } + + @JsonCreator + public static RandomList create(Map map) throws JsonProcessingException { + if(map.containsKey("pick") && map.containsKey("choices")) { + return RandomPickList.create(map); + } + + if(map.containsKey("concat")) { + return ConcatList.create(map); + } + + throw new JsonMappingException("Could not infer the kind of random list."); + } + + public static RandomList create(Object o) throws JsonProcessingException { + if (o instanceof List) return create((List) o); + if (o instanceof Map) return create((Map) o); + + else throw new JsonMappingException("Value is not a random list!"); + } + + protected T createItem(Object obj) throws JsonProcessingException { + return JsonHelper.readValue(obj, new TypeReference() {}); + } + + public List toConcrete(Random random) throws ConcretionFailureException { + ConstantList finalList = next(random); + + List result = new ArrayList<>(finalList.size()); + for (Object o : finalList.getList()) { + result.add((T) o); + } + + return result; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java new file mode 100644 index 00000000..6e2c96ea --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java @@ -0,0 +1,47 @@ +package brainwine.gameserver.util.randomobject; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; + +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.util.PickRandom; +import brainwine.shared.JsonHelper; + +public class RandomPickList extends RandomList { + + RandomList options; + RandomInteger pick; + + public static RandomPickList create(Map inp) throws JsonProcessingException { + RandomList options; + + Object inpChoices = inp.get("choices"); + if(inpChoices instanceof List) options = RandomList.create((List) inpChoices); + else if(inpChoices instanceof Map) options = RandomList.create((Map) inpChoices); + + else throw new JsonMappingException("Random pick list does not have a list of choices."); + + RandomInteger pick = JsonHelper.readValue(inp.get("pick"), RandomInteger.class); + return new RandomPickList(options, pick); + } + + public RandomPickList(RandomList options, RandomInteger pick) { + this.options = options; + this.pick = pick; + } + + @Override + public ConstantList next(Random random) throws ConcretionFailureException { + ConstantList list = options.next(random); + int count = pick.next(random); + List chosen = PickRandom.sampleWithoutReplacement(random, list.getList(), count); + + return new ConstantList<>(chosen); + } + +} From 964bda3fea515a49fde0dc88d13ed639addcef74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sat, 26 Oct 2024 03:33:16 +0200 Subject: [PATCH 04/36] add daily quests --- .../gameserver/item/LazyItemGetter.java | 14 +++ .../gameserver/player/Inventory.java | 4 + .../brainwine/gameserver/player/Player.java | 18 +++- .../gameserver/player/PlayerConfigFile.java | 15 +-- .../gameserver/quest/DailyQuests.java | 29 ++++++ .../gameserver/quest/PlayerQuests.java | 30 ++++-- .../gameserver/quest/QuestEvents.java | 43 ++++++-- .../gameserver/quest/QuestProgress.java | 12 +-- .../brainwine/gameserver/quest/Quests.java | 11 ++- .../gameserver/quest/RandomQuest.java | 7 +- .../gameserver/quest/RandomQuestReward.java | 13 +-- .../gameserver/quest/RandomQuests.java | 26 +++-- .../quest/randomquests/Collect.java | 77 ++++++++++----- .../gameserver/quest/randomquests/Kill.java | 51 +++++++--- .../gameserver/util/ValueWithExpiry.java | 78 +++++++++++++++ .../util/randomobject/ConcatList.java | 37 +++---- .../util/randomobject/ConstantList.java | 8 +- .../randomobject/ObjectMapperProvider.java | 7 ++ .../util/randomobject/RandomInteger.java | 8 +- .../util/randomobject/RandomList.java | 52 +--------- .../randomobject/RandomListDeserializer.java | 97 +++++++++++++++++++ .../util/randomobject/RandomListItemType.java | 12 +++ .../RandomListObjectMapperProvider.java | 12 +++ .../util/randomobject/RandomPickList.java | 35 +++---- .../gameserver/zone/EntityManager.java | 1 + .../src/main/resources/random-quests.json | 64 ++++++++++++ 26 files changed, 568 insertions(+), 193 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/ValueWithExpiry.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/ObjectMapperProvider.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListDeserializer.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListItemType.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListObjectMapperProvider.java create mode 100644 gameserver/src/main/resources/random-quests.json diff --git a/gameserver/src/main/java/brainwine/gameserver/item/LazyItemGetter.java b/gameserver/src/main/java/brainwine/gameserver/item/LazyItemGetter.java index 6d75bb8f..40b069e2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/LazyItemGetter.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/LazyItemGetter.java @@ -1,7 +1,14 @@ package brainwine.gameserver.item; import brainwine.gameserver.util.LazyGetter; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.io.IOException; + +@JsonSerialize(using = LazyItemGetter.Serializer.class) public class LazyItemGetter extends LazyGetter { public LazyItemGetter(String in) { @@ -12,4 +19,11 @@ public LazyItemGetter(String in) { public Item load() { return ItemRegistry.getItem(in); } + + public static class Serializer extends JsonSerializer { + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(((LazyItemGetter)value).get().getId()); + } + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java index 3659e072..2b5399b8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java @@ -8,6 +8,7 @@ import java.util.Map.Entry; import java.util.stream.Collectors; +import brainwine.gameserver.quest.QuestEvents; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; @@ -94,6 +95,9 @@ public void addItem(Item item, int quantity) { public void addItem(Item item, int quantity, boolean sendMessage) { setItem(item, getQuantity(item) + quantity, sendMessage); + if(quantity > 0 && player != null && item != null) { + QuestEvents.handleCollectInventory(player, item, quantity); + } } public void removeItem(Item item) { diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index c446f232..05ccec54 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -14,6 +14,9 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import brainwine.gameserver.quest.DailyQuests; +import brainwine.gameserver.quest.Quest; +import brainwine.gameserver.util.ValueWithExpiry; import com.fasterxml.jackson.annotation.JsonCreator; import brainwine.gameserver.GameConfiguration; @@ -107,6 +110,7 @@ public class Player extends Entity implements CommandExecutor { private Map> bumpedSkills; private Map appearance; private Map questProgresses = new HashMap<>(); + private ValueWithExpiry dailyQuest = ValueWithExpiry.getExpired(); private final Map settings = new HashMap<>(); private final Set activeChunks = new HashSet<>(); private final Map> dialogs = new HashMap<>(); @@ -127,7 +131,7 @@ public class Player extends Entity implements CommandExecutor { private long lastTrackedEntityUpdate; private Zone nextZone; private Connection connection; - + protected Player(String documentId, PlayerConfigFile config) { super(config.getCurrentZone()); this.documentId = documentId; @@ -152,6 +156,7 @@ protected Player(String documentId, PlayerConfigFile config) { this.bumpedSkills = config.getBumpedSkills(); this.appearance = config.getAppearance(); this.questProgresses = config.getQuestProgresses(); + this.dailyQuest = config.getDailyQuest(); health = getMaxHealth(); inventory.setPlayer(this); statistics.setPlayer(this); @@ -212,6 +217,8 @@ public void tick(float deltaTime) { sendMessage(new EntityPositionMessage(trackedEntities)); lastTrackedEntityUpdate = now; } + + DailyQuests.tryIssueDailyQuest(this); } @Override @@ -477,6 +484,7 @@ public void changeZone(Zone zone, int x, int y) { sendMessage(new EventMessage("playerWillChangeZone", null)); kick("Teleporting...", true); QuestEvents.handleEnterZone(this, zone); + DailyQuests.tryIssueDailyQuest(this); } public void showDialog(Dialog dialog) { @@ -1135,6 +1143,14 @@ public Map getAppearance() { public Map getQuestProgresses() { return questProgresses; } + + public ValueWithExpiry getDailyQuest() { + return dailyQuest; + } + + public void setDailyQuest(ValueWithExpiry dailyQuest) { + this.dailyQuest = dailyQuest; + } public void setSkillLevel(Skill skill, int level) { skills.put(skill, level); diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java index cce241d6..b6dbf82e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java @@ -1,12 +1,9 @@ package brainwine.gameserver.player; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import brainwine.gameserver.quest.Quest; +import brainwine.gameserver.util.ValueWithExpiry; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSetter; @@ -42,6 +39,7 @@ public class PlayerConfigFile { private Map> bumpedSkills = new HashMap<>(); private Map appearance = new HashMap<>(); private Map questProgresses = new HashMap<>(); + private ValueWithExpiry dailyQuest = ValueWithExpiry.getExpired(); public PlayerConfigFile(Player player) { this.name = player.getName(); @@ -66,6 +64,7 @@ public PlayerConfigFile(Player player) { this.bumpedSkills = player.getBumpedSkills(); this.appearance = player.getAppearance(); this.questProgresses = player.getQuestProgresses(); + this.dailyQuest = player.getDailyQuest(); } @JsonCreator @@ -172,4 +171,8 @@ public Map getAppearance() { public Map getQuestProgresses() { return questProgresses; } + + public ValueWithExpiry getDailyQuest() { + return dailyQuest; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java new file mode 100644 index 00000000..662d88ee --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java @@ -0,0 +1,29 @@ +package brainwine.gameserver.quest; + +import brainwine.gameserver.player.Player; +import brainwine.gameserver.util.ValueWithExpiry; + +public class DailyQuests { + public static void tryIssueDailyQuest(Player player) { + ValueWithExpiry current = player.getDailyQuest(); + + if(current == null || current.isExpired()) { + Quest newQuest = RandomQuests.generateRandomPlayerQuest(player); + newQuest.setTitle("Daily Quest"); + + if(newQuest == null) return; + + if(current.getValue() != null) { + String oldQuestId = current.getValue().getId(); + PlayerQuests.cancelQuest(player, oldQuestId); + + player.getQuestProgresses().remove(oldQuestId); + PlayerQuests.sendPlayerCancelQuestMessage(player, oldQuestId); + } + + player.setDailyQuest(new ValueWithExpiry<>(newQuest, "24h")); + PlayerQuests.beginQuest(player, newQuest); + player.notify("You have a new daily quest! Check your quests menu in the top left to see what you need to do!"); + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java index 981ea3db..30bbc9d1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java @@ -12,6 +12,17 @@ public class PlayerQuests { private PlayerQuests() {} + public static void deleteUnknownQuestProgress(Player player) { + if(player.getQuestProgresses() == null) return; + + for(String questId : new ArrayList<>(player.getQuestProgresses().keySet())) { + if(Quests.get(player, questId) == null) { + player.getQuestProgresses().remove(questId); + } + } + + } + public static void beginQuest(Player player, Quest quest) { if(player == null) return; @@ -105,8 +116,7 @@ public static void finishQuest(Player player, Quest quest) { progress.markAsComplete(); sendPlayerQuestMessage(player, progress); - // TODO: this should normally happen without the player having to return to the android - performAction(player, quest, QuestAction.Type.DONE); + } public static void performAction(Player player, Quest quest, QuestAction.Type actionType) { @@ -125,22 +135,30 @@ public static void sendInitialPlayerQuestMessages(Player player) { Map progresses = player.getQuestProgresses(); if(progresses == null) return; - + + Quest dailyQuest = player.getDailyQuest() == null ? null : player.getDailyQuest().getValue(); + if(player.getDailyQuest() != null && !player.getDailyQuest().isExpired() && dailyQuest != null) { + QuestProgress progress = player.getQuestProgresses().get(dailyQuest.getId()); + if(progress != null) sendPlayerQuestMessage(player, progress); + } + for(QuestProgress progress : progresses.values()) { sendPlayerQuestMessage(player, progress); } } public static void sendPlayerQuestMessage(Player player, QuestProgress progress) { - Quest quest = Quests.get(progress.getQuestId()); + if(progress == null) return; + + Quest quest = Quests.get(player, progress.getQuestId()); if(quest == null) return; // TODO: detect mobile player properly if(player.isV3()) { - player.sendMessage(new QuestMessage(quest.getPcDetails(), progress.getClientStatus())); + player.sendMessage(new QuestMessage(quest.getPcDetails(), progress.getClientStatus(player))); } else { - player.sendMessage(new QuestMessage(quest.getMobileDetails(), progress.getClientStatus())); + player.sendMessage(new QuestMessage(quest.getMobileDetails(), progress.getClientStatus(player))); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java index cd9749d5..3fd1fc22 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java @@ -6,6 +6,7 @@ import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.item.Item; import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.Zone; @@ -66,11 +67,11 @@ public static void handleEvent(Player player, Object... pattern) { for(Map.Entry questProgressEntry : player.getQuestProgresses().entrySet()) { String questId = questProgressEntry.getKey(); QuestProgress questProgress = questProgressEntry.getValue(); - Quest quest = Quests.get(questId); + Quest quest = Quests.get(player, questId); int i = 0; boolean anyProgress = false; - for(QuestTask task : quest.getTasks()) { + if(pattern != null) for(QuestTask task : quest.getTasks()) { if(task.getEvents() == null) continue; if(!task.doesQualify(player)) { @@ -78,14 +79,28 @@ public static void handleEvent(Player player, Object... pattern) { } for(List event : task.getEvents()) { - if(patternMatch(event, pattern)) { - questProgress.getTaskProgresses().set(i, questProgress.getTaskProgress(i) + 1); - - if(event.size() >= 3 && "interact".equals(event.get(0)) && "name".equals(event.get(1))) { - PlayerQuests.performAction(player, quest, QuestAction.Type.INTERACT); + try { + if(patternMatch(event, pattern)) { + if(event.size() >= 4 && "collect_item".equals(event.get(0)) && "id".equals(event.get(1))) { + Integer amount = (Integer) pattern[3]; + if(amount != null && amount > 0) { + questProgress.getTaskProgresses().set(i, questProgress.getTaskProgress(i) + amount); + } + anyProgress = true; + break; + } + + questProgress.getTaskProgresses().set(i, questProgress.getTaskProgress(i) + 1); + + if(event.size() >= 3 && "interact".equals(event.get(0)) && "name".equals(event.get(1))) { + PlayerQuests.performAction(player, quest, QuestAction.Type.INTERACT); + } + + anyProgress = true; + break; } - anyProgress = true; - break; + } catch(Exception e) { + e.printStackTrace(); } } i++; @@ -94,9 +109,19 @@ public static void handleEvent(Player player, Object... pattern) { if(anyProgress) { PlayerQuests.sendPlayerQuestMessage(player, questProgress); } + + if(PlayerQuests.canFinishQuest(player, quest)) { + PlayerQuests.finishQuest(player, quest); + PlayerQuests.performAction(player, quest, QuestAction.Type.DONE); + } + } } + public static void handleCollectInventory(Player player, Item item, int quantity) { + handleEvent(player, "collect_item", "id", item.getId(), quantity); + } + public static void handleEnterZone(Player player, Zone zone) { handleEvent(player, "entered", "zone_name", zone.getName()); } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java index 7377bc75..4a536b7c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java @@ -31,8 +31,8 @@ public QuestProgress(String questId, List taskProgresses) { } @JsonIgnore - public Quest getQuest() { - return Quests.get(getQuestId()); + public Quest getQuest(Player player) { + return Quests.get(player, getQuestId()); } public int getTaskProgress(int index) { @@ -73,7 +73,7 @@ public List getDialogSection(Player player, boolean canFinishQues DialogSection mainSection = new DialogSection(); result.add(mainSection); - Quest quest = getQuest(); + Quest quest = getQuest(player); if(quest == null) { mainSection.setTitle("QUEST NOT FOUND"); @@ -109,9 +109,9 @@ public List getDialogSection(Player player, boolean canFinishQues } @JsonIgnore - public Map getClientStatus() { + public Map getClientStatus(Player player) { Map result = new HashMap<>(); - Quest quest = Quests.get(getQuestId()); + Quest quest = Quests.get(player, getQuestId()); List completedIndices = new ArrayList<>(); @@ -137,7 +137,7 @@ public Map getClientStatus() { public String tryCancelOtherwiseReason(Player player) { if(player.isGodMode()) return null; - Quest quest = getQuest(); + Quest quest = getQuest(player); if(quest == null) return null; String reason = null; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java index 8f87152f..154d31e0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java @@ -21,7 +21,6 @@ import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.PickRandom; import brainwine.shared.JsonHelper; -import io.netty.util.internal.ThreadLocalRandom; public class Quests { public static Map> questMaps = new HashMap<>(); @@ -89,6 +88,16 @@ public static void loadQuests() { logger.info("Successfully loaded {} quests", counter); } + public static Quest get(Player player, String questId) { + if(player.getDailyQuest() != null + && player.getDailyQuest().getValue() != null + && player.getDailyQuest().getValue().getId().equals(questId)) { + return player.getDailyQuest().getValue(); + } + + return get(questId); + } + public static Quest get(String questId) { Map targetMap = null; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java index 5cc06345..75406788 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java @@ -4,9 +4,9 @@ import brainwine.gameserver.player.Player; import brainwine.gameserver.quest.randomquests.*; +import brainwine.gameserver.util.randomobject.RandomListObjectMapperProvider; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import io.netty.util.internal.ThreadLocalRandom; @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, @@ -18,15 +18,12 @@ @JsonSubTypes.Type(value = Kill.class, name = "kill"), @JsonSubTypes.Type(value = Collect.class, name = "collect"), }) +@RandomListObjectMapperProvider(RandomQuests.MapperProvider.class) public abstract class RandomQuest { private String type = "none"; private int tier = 1; private int frequency = 1; - public void nextQuest(Player player, Quest quest) { - nextQuest(ThreadLocalRandom.current(), player, quest); - } - public abstract void nextQuest(Random random, Player player, Quest quest); public String getType() { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java index cab1541a..3e7d12a2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java @@ -2,27 +2,24 @@ import java.util.Random; +import brainwine.gameserver.util.randomobject.*; import com.fasterxml.jackson.annotation.JsonProperty; -import brainwine.gameserver.util.randomobject.Arbitrary; -import brainwine.gameserver.util.randomobject.ConcretionFailureException; -import brainwine.gameserver.util.randomobject.RandomInteger; -import brainwine.gameserver.util.randomobject.RandomList; - public class RandomQuestReward implements Arbitrary { @JsonProperty("xp") RandomInteger xp = null; @JsonProperty("crowns") RandomInteger crowns = null; @JsonProperty("loot_categories") + @RandomListItemType(String.class) RandomList lootCategories = null; @Override public QuestReward next(Random random) throws ConcretionFailureException { QuestReward result = new QuestReward(); - if (xp != null) result.setXp(xp.next(random)); - if (crowns != null) result.setCrowns(crowns.next(random)); - if (lootCategories != null) result.setLootCategories(lootCategories.toConcrete(random)); + if(xp != null) result.setXp(xp.next(random)); + if(crowns != null) result.setCrowns(crowns.next(random)); + if(lootCategories != null) result.setLootCategories(lootCategories.toConcrete(random)); return result; } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java index 11d85c19..1a1dd1ce 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java @@ -7,8 +7,10 @@ import java.util.*; import java.util.stream.Collectors; -import com.fasterxml.jackson.annotation.JsonIgnore; +import brainwine.gameserver.util.randomobject.ObjectMapperProvider; +import brainwine.shared.JsonHelper; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,7 +18,6 @@ import brainwine.gameserver.player.Player; import brainwine.gameserver.resource.ResourceFinder; import brainwine.gameserver.util.WeightedMap; -import brainwine.shared.JsonHelper; import io.netty.util.internal.ThreadLocalRandom; public class RandomQuests { @@ -32,12 +33,21 @@ private static class Configuration { List randomQuests = new ArrayList<>(); } + public static class MapperProvider implements ObjectMapperProvider { + @Override + public ObjectMapper get() { + return JsonHelper.MAPPER; + } + } + public static void loadConfiguration() { - logger.info(SERVER_MARKER, "Loading loot tables ..."); + logger.info(SERVER_MARKER, "Loading random quest configuration..."); try { URL url = ResourceFinder.getResourceUrl("random-quests.json"); - configuration = JsonHelper.readValue(url, new TypeReference(){}); + configuration = JsonHelper.MAPPER.readValue(url, new TypeReference(){}); + + logger.info(String.format("Successfully loaded %d random quests.", configuration.randomQuests.size())); } catch (IOException e) { logger.error(SERVER_MARKER, "Failed to load random quests", e); } @@ -48,8 +58,8 @@ public static int getMaxTier(Player player) { int tier = 1; int level = player.getLevel(); - for (Map.Entry e : configuration.levelMaxTiers.entrySet()) { - if (level >= e.getValue() && tier < e.getKey()) { + for(Map.Entry e : configuration.levelMaxTiers.entrySet()) { + if(level >= e.getValue() && tier < e.getKey()) { tier = e.getKey(); } } @@ -62,6 +72,10 @@ public static Quest generateRandomPlayerQuest(Player player) { } public static Quest generateRandomPlayerQuest(Random random, Player player) { + if(configuration.randomQuests.isEmpty()) { + return null; + } + int maxTier = getMaxTier(player); List candidates = configuration.randomQuests.stream() .filter(q -> q.getTier() <= maxTier) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java index fd29ce79..1480c84a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -11,53 +11,69 @@ public class Collect extends RandomQuest { @JsonProperty("items") + @RandomListItemType(CollectItem.class) private RandomList items; @JsonProperty("reward") private RandomQuestReward reward = null; @JsonProperty("task_description") private String taskDescription = null; - private class CollectItem implements Arbitrary { - RandomList item = new ConstantList<>(); + @RandomListObjectMapperProvider(RandomQuests.MapperProvider.class) + private static class CollectItem { + @JsonProperty("items") + @RandomListItemType(String.class) + RandomList items = new ConstantList<>(); + @JsonProperty("count") RandomInteger count = new RandomInteger(1); @Override - public QuestTask next(Random random) throws ConcretionFailureException { - Map requirements = new HashMap<>(); + public String toString() { + return "CollectItem{" + + "items=" + items + + ", count=" + count + + '}'; + } + } - for (String oneItem : item.toConcrete(random)) { - requirements.put(oneItem, count.next(random)); - } + public QuestTask nextQuestTask(Random random, CollectItem collectItem) throws ConcretionFailureException { + List> events = new ArrayList<>(); - String myTaskDescription; - if (taskDescription == null) { - myTaskDescription = "Collect" + requirements.entrySet().stream().map(e -> { - Item item = Item.get(e.getKey()); - String itemTitle; - if (item == null) { - itemTitle = e.getKey(); - } else { - itemTitle = item.getTitle(); - } - return e.getValue() + " x " + itemTitle; - }).collect(Collectors.joining(", ")); - } else { - myTaskDescription = taskDescription; - } + for(String oneItem : collectItem.items.toConcrete(random)) { + events.add(Arrays.asList("collect_item", "id", oneItem, null)); + } - return new QuestTask() - .setDescription(myTaskDescription) - .setCollectInventory(new QuestTaskCollectInventory(requirements)); + int quantity = collectItem.count.next(random); + String myTaskDescription; + if(taskDescription == null) { + myTaskDescription = "Collect " + quantity + " x " + events.stream().map(e -> { + String itemId = (String) e.get(2); + Item item = Item.get(itemId); + String itemTitle; + if(item == null) { + itemTitle = itemId; + } else { + itemTitle = item.getTitle(); + } + return itemTitle; + }).collect(Collectors.joining(" or ")); + } else { + myTaskDescription = taskDescription; } + + return new QuestTask() + .setDescription(myTaskDescription) + .setEvents(events) + .setQuantity(quantity); } @Override public void nextQuest(Random random, Player player, Quest quest) { try { + quest.setDescription(RandomQuests.getString(random, "collect_description")); List collectItemList = items.toConcrete(random); List tasks = new ArrayList<>(collectItemList.size()); for(CollectItem c : collectItemList) { - tasks.add(c.next(random)); + tasks.add(nextQuestTask(random, c)); } quest.setTasks(tasks); if(reward != null) quest.setReward(reward.next(random)); @@ -65,4 +81,13 @@ public void nextQuest(Random random, Player player, Quest quest) { throw new IllegalStateException("Concretion failure!"); } } + + @Override + public String toString() { + return "Collect{" + + "items=" + items + + ", reward=" + reward + + ", taskDescription='" + taskDescription + '\'' + + '}'; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index 6ca5e1c3..5f25877c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -1,9 +1,5 @@ package brainwine.gameserver.quest.randomquests; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -12,20 +8,20 @@ import brainwine.gameserver.player.Player; import brainwine.gameserver.quest.*; -import brainwine.gameserver.util.randomobject.ConcretionFailureException; -import brainwine.gameserver.util.randomobject.ConstantList; -import brainwine.gameserver.util.randomobject.RandomInteger; -import brainwine.gameserver.util.randomobject.RandomList; +import brainwine.gameserver.util.randomobject.*; import com.fasterxml.jackson.annotation.JsonProperty; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import org.apache.commons.text.WordUtils; public class Kill extends RandomQuest { @JsonProperty("categories") + @RandomListItemType(String.class) private RandomList categories = null; @JsonProperty("codes") + @RandomListItemType(RandomInteger.class) private RandomList codes = null; @JsonProperty("entities") + @RandomListItemType(String.class) private RandomList entityIds = null; @JsonProperty("quantity") private RandomInteger quantity = null; @@ -40,12 +36,13 @@ public class Kill extends RandomQuest { @JsonProperty("reward") private RandomQuestReward reward = null; @JsonProperty("actions") + @RandomListItemType(String.class) private RandomList actions = new ConstantList<>(Arrays.asList("kill")); private List> getKillEvents(List actions, String type, List values) { List> events = new ArrayList<>(actions.size() * values.size()); - for (String action : actions) { - for (Object value : values) { + for(String action : actions) { + for(Object value : values) { events.add(Arrays.asList(action, type, value)); } } @@ -68,7 +65,7 @@ public void setTaskDescription(QuestTask task, String taskDescription, String ac } String message; - if (values.size() == 1) { + if(values.size() == 1) { message = actionMessage + " an entity " + concat + times; } else { message = actionMessage + " any of the entities " + concat + times; @@ -83,7 +80,6 @@ public void setTaskDescription(QuestTask task, String taskDescription, String ac @Override public void nextQuest(Random random, Player player, Quest quest) { try { - quest.setTitle("Daily Quest"); quest.setDescription(RandomQuests.getString(random, "kill_description")); List tasks = new ArrayList<>(); @@ -91,7 +87,7 @@ public void nextQuest(Random random, Player player, Quest quest) { String actionMessage = WordUtils.capitalize(String.join(" or ", actions)); - if (categories != null) { + if(categories != null) { List values = categories.toConcrete(random); int quantity = defaultIfNull(this.quantity, categoryQuantity).next(random); QuestTask task = new QuestTask() @@ -101,7 +97,7 @@ public void nextQuest(Random random, Player player, Quest quest) { tasks.add(task); } - if (entityIds != null) { + if(entityIds != null) { List values = entityIds.toConcrete(random); int quantity = defaultIfNull(this.quantity, entityIdQuantity).next(random); QuestTask task = new QuestTask() @@ -111,7 +107,7 @@ public void nextQuest(Random random, Player player, Quest quest) { tasks.add(task); } - if (codes != null) { + if(codes != null) { List values = codes.toConcrete(random).stream() .map(i -> i.next(random)) .collect(Collectors.toList()); @@ -124,10 +120,33 @@ public void nextQuest(Random random, Player player, Quest quest) { } quest.setTasks(tasks); - if (reward != null) quest.setReward(reward.next(random)); + if(reward != null) quest.setReward(reward.next(random)); } catch (ConcretionFailureException e) { throw new IllegalStateException("Concretion failure!"); } } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Kill{ "); + + sb.append("taskDescription='" + taskDescription + '\'' + + ", reward=" + reward + + ", actions=" + actions); + + if(categories != null) { + sb.append(", categories=" + categories + ", categoryQuantity=" + categoryQuantity); + } + if(entityIds != null) { + sb.append(", entityIds=" + entityIds + ", entityIdQuantity=" + entityIdQuantity); + } + if(codes != null) { + sb.append(", codes=" + codes + ", codeQuantity=" + codeQuantity); + } + + sb.append(" }"); + + return sb.toString(); + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/util/ValueWithExpiry.java b/gameserver/src/main/java/brainwine/gameserver/util/ValueWithExpiry.java new file mode 100644 index 00000000..7a67f138 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/ValueWithExpiry.java @@ -0,0 +1,78 @@ +package brainwine.gameserver.util; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Calendar; + +public class ValueWithExpiry { + @JsonProperty("expires_at") + private Calendar expiresAt; + @JsonProperty("value") + private T value; + + private Calendar now() { + return Calendar.getInstance(); + } + + public ValueWithExpiry() {} + + /**Make the value expire within the specified time. This uses the system clock to compute the expiration date. + * + * @param value wrapped value + * @param duration duration to be parsed with {@link DateTimeUtils#parseFormattedDuration(String)} + */ + public ValueWithExpiry(T value, String duration) { + this.expiresAt = now(); + this.expiresAt.add(Calendar.MINUTE, DateTimeUtils.parseFormattedDuration(duration)); + this.value = value; + } + + /**Make the value expired at the specified time. + * + * @param value wrapped value + * @param expiresAt duration + */ + public ValueWithExpiry(T value, Calendar expiresAt) { + this.expiresAt = expiresAt; + this.value = value; + } + + /**Get a ValueWithExpiry that is guaranteed to be expired. Do not use the contained value in the returned instance. + * + * @return an expired ValueWithExpiry instance + * @param type of the contained value. Assume the value to be null. + */ + public static ValueWithExpiry getExpired() { + ValueWithExpiry value = new ValueWithExpiry<>(); + value.expiresAt = null; + value.value = null; + return value; + } + + /**Check if the value is expired according to the system clock + * + * @return whether the value is expired. Note that it will return true also if the expiration date is null. + */ + @JsonIgnore + public boolean isExpired() { + return isExpired(Calendar.getInstance()); + } + + /**Check if the value is expired according to some assumed current time + * + * @param now the assumed current time + * @return whether the value is expired. Note that it will return true also if the expiration date is null. + */ + public boolean isExpired(Calendar now) { + return expiresAt == null || expiresAt.before(now); + } + + /**Return the wrapped value. This doesn't check if the value is expired + * + * @return + */ + public T getValue() { + return value; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java index a2039fcb..2378f598 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java @@ -2,46 +2,31 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Random; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; +import java.util.stream.Collectors; public class ConcatList extends RandomList { - List> lists; + private List> concat; - public ConcatList() {} - public ConcatList(List> lists) { - this.lists = lists; - } - - public static ConcatList create(Map inp) throws JsonProcessingException { - Object inpList = inp.get("concat"); - - if (inpList instanceof List) { - List> concat = new ArrayList<>(); - - for (Object o : (List)inpList) { - concat.add(RandomList.create(o)); - } - - return new ConcatList<>(concat); - } - - throw new JsonMappingException("Concat list doesn't have a list of lists."); + public ConcatList(List> concat) { + this.concat = concat; } @Override public ConstantList next(Random random) throws ConcretionFailureException { - if (lists == null) return null; + if(concat == null) return new ConstantList<>(); List result = new ArrayList(); - for (RandomList l : lists) { + for(RandomList l : concat) { result.addAll(l.next(random).getList()); } return new ConstantList(result); } + + @Override + public String toString() { + return "ConcatList(" + concat.stream().map(r -> r.toString()).collect(Collectors.joining(", ")) + ")"; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java index 931b4eac..bde3bd3b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java @@ -3,9 +3,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.stream.Collectors; public class ConstantList extends RandomList { - List list; + private List list; public ConstantList() { this.list = new ArrayList<>(); @@ -27,4 +28,9 @@ public int size() { public List getList() { return list; } + + @Override + public String toString() { + return "ConstantList(" + list.stream().map(r -> r.toString()).collect(Collectors.joining(", ")) + ")"; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ObjectMapperProvider.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ObjectMapperProvider.java new file mode 100644 index 00000000..452cc3e4 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ObjectMapperProvider.java @@ -0,0 +1,7 @@ +package brainwine.gameserver.util.randomobject; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public interface ObjectMapperProvider { + ObjectMapper get(); +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java index 151012f0..cba4d6dd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java @@ -22,15 +22,15 @@ public RandomInteger(Integer value) { @JsonCreator public RandomInteger(@JsonProperty("random_between") List randomBetween ) throws JsonMappingException { - if (!(randomBetween.size() == 2 || randomBetween.size() == 3)) { + if(!(randomBetween.size() == 2 || randomBetween.size() == 3)) { throw new JsonMappingException("Invalid input for random_between: provide either 2 or 3 constant numbers"); } - if (randomBetween.get(0) > randomBetween.get(1)) { + if(randomBetween.get(0) > randomBetween.get(1)) { throw new JsonMappingException("Invalid input for random_between: range min is greater than range max"); } - if (randomBetween.size() == 3 && randomBetween.get(2) <= 0) { + if(randomBetween.size() == 3 && randomBetween.get(2) <= 0) { throw new JsonMappingException("Invalid input for random_between: only positive step values are allowed"); } this.randomBetween = randomBetween; @@ -38,7 +38,7 @@ public RandomInteger(@JsonProperty("random_between") List randomBetween @Override public Integer next(Random random) { - if (this.isConcrete) { + if(this.isConcrete) { return value; } else { int step = 1; diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java index 341ab7a0..d57870c0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java @@ -1,60 +1,14 @@ package brainwine.gameserver.util.randomobject; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Random; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonMappingException; -import brainwine.shared.JsonHelper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +@JsonDeserialize(using = RandomListDeserializer.class) public abstract class RandomList implements Arbitrary> { - @JsonCreator - public static RandomList create(List list) throws JsonProcessingException { - ConstantList result = new ConstantList(); - - for (Object o : list) { - result.getList().add(result.createItem(o)); - } - - return result; - } - - @JsonCreator - public static RandomList create(Map map) throws JsonProcessingException { - if(map.containsKey("pick") && map.containsKey("choices")) { - return RandomPickList.create(map); - } - - if(map.containsKey("concat")) { - return ConcatList.create(map); - } - - throw new JsonMappingException("Could not infer the kind of random list."); - } - - public static RandomList create(Object o) throws JsonProcessingException { - if (o instanceof List) return create((List) o); - if (o instanceof Map) return create((Map) o); - - else throw new JsonMappingException("Value is not a random list!"); - } - - protected T createItem(Object obj) throws JsonProcessingException { - return JsonHelper.readValue(obj, new TypeReference() {}); - } - public List toConcrete(Random random) throws ConcretionFailureException { ConstantList finalList = next(random); - - List result = new ArrayList<>(finalList.size()); - for (Object o : finalList.getList()) { - result.add((T) o); - } - - return result; + return finalList.getList(); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListDeserializer.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListDeserializer.java new file mode 100644 index 00000000..6322bfeb --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListDeserializer.java @@ -0,0 +1,97 @@ +package brainwine.gameserver.util.randomobject; + +import brainwine.shared.JsonHelper; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; + +public class RandomListDeserializer extends StdDeserializer> implements ContextualDeserializer { + private Class itemType; + private ObjectMapper mapper; + + public RandomListDeserializer() { + this(null, null); + } + + public RandomListDeserializer(Class itemType, ObjectMapper mapper) { + super(RandomList.class); + this.itemType = itemType; + this.mapper = mapper; + } + + @Override + public RandomList deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException + { + JsonNode node = parser.readValueAsTree(); + + return deserialize(node, context); + } + + private RandomList deserialize(JsonNode node, DeserializationContext context) throws IOException { + if(node.isObject()) { + if(node.hasNonNull("choices")) { + RandomList choices = deserialize(node.get("choices"), context); + if(node.has("pick")) { + RandomInteger pick = mapper.readValue(mapper.treeAsTokens(node.get("pick")), RandomInteger.class); + return new RandomPickList<>(choices, pick); + } else { + return new RandomPickList<>(choices); + } + + } + + if(node.hasNonNull("concat")) { + List> concatList = new ArrayList<>(); + JsonNode concatNode = node.get("concat"); + for(int i = 0; i < concatNode.size(); i++) { + JsonNode current = concatNode.get(i); + concatList.add(deserialize(current, context)); + } + return new ConcatList<>(concatList); + } + } + + if(node.isArray()) { + List arr = new ArrayList<>(); + for(int i = 0; i < node.size(); i++) { + JsonNode current = node.get(i); + arr.add(mapper.readValue(mapper.treeAsTokens(current), itemType)); + } + return new ConstantList<>(arr); + } + + return null; + } + + /** Standard procedure to resolve some annotation. */ + private T getAnnotation(BeanProperty property, Class clazz) throws Exception { + T value = property.getMember().getAnnotation(clazz); + if(value == null) value = property.getMember().getDeclaringClass().getAnnotation(clazz); + if(value == null) throw new Exception(clazz.getName() + " is missing!"); + + return value; + } + + @Override + public JsonDeserializer createContextual(DeserializationContext context, BeanProperty property) { + try { + if(property == null) { + throw new Exception("BeanProperty is missing!"); + } + + RandomListItemType itemType = getAnnotation(property, RandomListItemType.class); + + return new RandomListDeserializer<>(itemType.value(), JsonHelper.MAPPER); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListItemType.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListItemType.java new file mode 100644 index 00000000..506ca510 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListItemType.java @@ -0,0 +1,12 @@ +package brainwine.gameserver.util.randomobject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +public @interface RandomListItemType { + Class value() default Object.class; +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListObjectMapperProvider.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListObjectMapperProvider.java new file mode 100644 index 00000000..3768d4ac --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomListObjectMapperProvider.java @@ -0,0 +1,12 @@ +package brainwine.gameserver.util.randomobject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +public @interface RandomListObjectMapperProvider { + Class value(); +} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java index 6e2c96ea..ce6e1ac3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java @@ -1,47 +1,36 @@ package brainwine.gameserver.util.randomobject; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Random; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; - -import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.PickRandom; -import brainwine.shared.JsonHelper; public class RandomPickList extends RandomList { - - RandomList options; + RandomList choices; RandomInteger pick; - public static RandomPickList create(Map inp) throws JsonProcessingException { - RandomList options; - - Object inpChoices = inp.get("choices"); - if(inpChoices instanceof List) options = RandomList.create((List) inpChoices); - else if(inpChoices instanceof Map) options = RandomList.create((Map) inpChoices); - - else throw new JsonMappingException("Random pick list does not have a list of choices."); - - RandomInteger pick = JsonHelper.readValue(inp.get("pick"), RandomInteger.class); - return new RandomPickList(options, pick); + public RandomPickList(RandomList choices) { + this.choices = choices; + this.pick = new RandomInteger(1); } - public RandomPickList(RandomList options, RandomInteger pick) { - this.options = options; + public RandomPickList(RandomList choices, RandomInteger pick) { + this.choices = choices; this.pick = pick; } @Override public ConstantList next(Random random) throws ConcretionFailureException { - ConstantList list = options.next(random); + ConstantList list = choices.next(random); int count = pick.next(random); List chosen = PickRandom.sampleWithoutReplacement(random, list.getList(), count); return new ConstantList<>(chosen); } + + @Override + public String toString() { + return "RandomPickList(" + pick + " of " + choices + ")"; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 65d510de..b102fbb5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -297,6 +297,7 @@ public void addEntity(Entity entity) { player.onZoneChanged(); players.put(entityId, player); playersByName.put(player.getName().toLowerCase(), player); + PlayerQuests.deleteUnknownQuestProgress(player); PlayerQuests.sendInitialPlayerQuestMessages(player); player.sendMessageToPeers(new EntityStatusMessage(player, EntityStatus.ENTERING)); player.sendMessageToPeers(new EntityPositionMessage(player)); diff --git a/gameserver/src/main/resources/random-quests.json b/gameserver/src/main/resources/random-quests.json new file mode 100644 index 00000000..4a87927d --- /dev/null +++ b/gameserver/src/main/resources/random-quests.json @@ -0,0 +1,64 @@ +{ + "strings": { + "kill_description": [ + "Today you got to kill some critters!", + "What time is it? Uh, we need to keep the game child-friendly.", + "Blow them up for some crowns!", + "Nothing better than relieving some stress by putting your weapons in good use!" + ], + "collect_description": [ + "Time for your daily item collecting.", + "The shelves in your father's thrift store fell empty. Help them stock it back up!", + "You had been scammed by Graptik, but don't be afraid. Today is the day you will get your items back.", + "Hot stuff in your area, waiting for you to collect, within the next 24 hours@" + ] + }, + "quests": [ + { + "frequency": 10, + "type": "kill", + "categories": [ "terrapus" ], + "quantity": { "random_between": [ 5, 10 ] }, + "reward": { + "xp": { "random_between": [ 50, 150, 10 ] }, + "crowns": 2 + } + }, + { + "frequency": 10, + "type": "kill", + "entities": { + "choices": [ + "automata/android", + "automata/cat", + "creature/armadillo" + ], + "pick": 2 + }, + "task_description": "Stop the bot uprisal! Smash 3 androids of any kind!", + "quantity": 3, + "reward": { + "xp": 100, + "crowns": 2 + } + }, + { + "frequency": 10, + "type": "collect", + "items": { + "choices": [ + { + "items": [ "mechanical/turret" ], + "count": { "random_between": [ 3, 5 ] } + }, + { + "items": [ "building/door-wood" ], + "count": 3 + } + ], + "pick": 1 + }, + "reward": { "xp": 100, "crowns": 2 } + } + ] +} From b941e14ecb07ca1000d9c37132d579de6efa68be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sun, 27 Oct 2024 00:40:26 +0200 Subject: [PATCH 05/36] change collect item tracking --- .../main/java/brainwine/gameserver/player/Inventory.java | 4 ---- .../src/main/java/brainwine/gameserver/player/Player.java | 1 + .../main/java/brainwine/gameserver/quest/QuestEvents.java | 2 +- .../gameserver/server/requests/BlockMineRequest.java | 7 +++++++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java index 2b5399b8..3659e072 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java @@ -8,7 +8,6 @@ import java.util.Map.Entry; import java.util.stream.Collectors; -import brainwine.gameserver.quest.QuestEvents; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; @@ -95,9 +94,6 @@ public void addItem(Item item, int quantity) { public void addItem(Item item, int quantity, boolean sendMessage) { setItem(item, getQuantity(item) + quantity, sendMessage); - if(quantity > 0 && player != null && item != null) { - QuestEvents.handleCollectInventory(player, item, quantity); - } } public void removeItem(Item item) { diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 05ccec54..deec701a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -1243,6 +1243,7 @@ public void awardLoot(Loot loot, DialogType dialogType) { loot.getItems().forEach((item, quantity) -> { inventory.addItem(item, quantity, true); + QuestEvents.handleCollectItem(this, item, quantity); section.addItem(new DialogListItem() .setItem(item.getCode()) .setText(String.format("%s x %s", item.getTitle(), quantity))); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java index 3fd1fc22..77682f8f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java @@ -118,7 +118,7 @@ public static void handleEvent(Player player, Object... pattern) { } } - public static void handleCollectInventory(Player player, Item item, int quantity) { + public static void handleCollectItem(Player player, Item item, int quantity) { handleEvent(player, "collect_item", "id", item.getId(), quantity); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 6ae44a71..f60a03c6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -15,6 +15,7 @@ import brainwine.gameserver.player.NotificationType; import brainwine.gameserver.player.Player; import brainwine.gameserver.player.Skill; +import brainwine.gameserver.quest.QuestEvents; import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.BlockChangeMessage; @@ -207,6 +208,12 @@ public void process(Player player) { if(!inventoryItem.isAir()) { player.getInventory().addItem(inventoryItem, quantity, true); + if(block.isNatural() && layer == Layer.FRONT) { + QuestEvents.handleCollectItem(player, inventoryItem, quantity); + if(!item.equals(inventoryItem)) { + QuestEvents.handleCollectItem(player, item, 1); + } + } } } From 384b30d798607ae4662f140ba728ed2349a08129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sun, 27 Oct 2024 01:02:33 +0200 Subject: [PATCH 06/36] java 8 compatibility --- .../brainwine/gameserver/util/randomobject/RandomInteger.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java index cba4d6dd..390c4767 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomInteger.java @@ -45,7 +45,9 @@ public Integer next(Random random) { if(randomBetween.size() == 3) { step = randomBetween.get(2); } - return step * random.nextInt(randomBetween.get(0) / step, randomBetween.get(1) / step + 1); + int lowerBound = randomBetween.get(0) / step; + int upperBound = randomBetween.get(1) / step + 1; + return step * random.nextInt(upperBound - lowerBound) + lowerBound; } } From 7967443ec8f3db7aaddf5a3523555938e65c6aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sun, 27 Oct 2024 01:32:07 +0200 Subject: [PATCH 07/36] java 8 compatibility 2 --- .../src/main/java/brainwine/gameserver/quest/RandomQuests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java index 1a1dd1ce..3d2f05ef 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java @@ -98,6 +98,6 @@ public static Quest generateRandomPlayerQuest(Random random, Player player) { public static String getString(Random random, String label) { List list = configuration.strings.get(label); - return list.get(random.nextInt(0, list.size())); + return list.get(random.nextInt(list.size())); } } From c236ccfcf4821da809f472aa3770576dfc26f162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 30 Oct 2024 00:28:08 +0100 Subject: [PATCH 08/36] multiple daily quests and aesthetic changes --- .../brainwine/gameserver/player/Player.java | 6 +-- .../gameserver/player/PlayerConfigFile.java | 4 +- .../gameserver/quest/DailyQuests.java | 44 +++++++++++++------ .../gameserver/quest/PlayerQuests.java | 35 +++++++++++---- .../brainwine/gameserver/quest/Quests.java | 9 ++-- .../gameserver/quest/RandomQuests.java | 11 ++++- .../quest/randomquests/Collect.java | 2 +- .../gameserver/quest/randomquests/Kill.java | 2 +- 8 files changed, 79 insertions(+), 34 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index deec701a..ef190f51 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -110,7 +110,7 @@ public class Player extends Entity implements CommandExecutor { private Map> bumpedSkills; private Map appearance; private Map questProgresses = new HashMap<>(); - private ValueWithExpiry dailyQuest = ValueWithExpiry.getExpired(); + private ValueWithExpiry> dailyQuest = ValueWithExpiry.getExpired(); private final Map settings = new HashMap<>(); private final Set activeChunks = new HashSet<>(); private final Map> dialogs = new HashMap<>(); @@ -1144,11 +1144,11 @@ public Map getQuestProgresses() { return questProgresses; } - public ValueWithExpiry getDailyQuest() { + public ValueWithExpiry> getDailyQuest() { return dailyQuest; } - public void setDailyQuest(ValueWithExpiry dailyQuest) { + public void setDailyQuest(ValueWithExpiry> dailyQuest) { this.dailyQuest = dailyQuest; } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java index b6dbf82e..410ac117 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java @@ -39,7 +39,7 @@ public class PlayerConfigFile { private Map> bumpedSkills = new HashMap<>(); private Map appearance = new HashMap<>(); private Map questProgresses = new HashMap<>(); - private ValueWithExpiry dailyQuest = ValueWithExpiry.getExpired(); + private ValueWithExpiry> dailyQuest = ValueWithExpiry.getExpired(); public PlayerConfigFile(Player player) { this.name = player.getName(); @@ -172,7 +172,7 @@ public Map getQuestProgresses() { return questProgresses; } - public ValueWithExpiry getDailyQuest() { + public ValueWithExpiry> getDailyQuest() { return dailyQuest; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java index 662d88ee..a526e0ba 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java @@ -1,29 +1,45 @@ package brainwine.gameserver.quest; +import java.util.ArrayList; +import java.util.List; + import brainwine.gameserver.player.Player; import brainwine.gameserver.util.ValueWithExpiry; public class DailyQuests { public static void tryIssueDailyQuest(Player player) { - ValueWithExpiry current = player.getDailyQuest(); - - if(current == null || current.isExpired()) { - Quest newQuest = RandomQuests.generateRandomPlayerQuest(player); - newQuest.setTitle("Daily Quest"); + ValueWithExpiry> currentV = player.getDailyQuest(); - if(newQuest == null) return; + if(currentV == null || currentV.isExpired()) { + List newQuests = new ArrayList<>(); + for(int i = 1; i <= RandomQuests.getConfiguration().dailyQuestCount; i++) { + Quest newQuest = RandomQuests.generateRandomPlayerQuest(player); + if(newQuest == null) return; + newQuest.setId("daily_player_quest_" + i); + newQuest.setTitle("Daily Quest #" + i); + newQuests.add(newQuest); + } - if(current.getValue() != null) { - String oldQuestId = current.getValue().getId(); - PlayerQuests.cancelQuest(player, oldQuestId); + if(currentV.getValue() != null) { + for(Quest current : currentV.getValue()) { + String oldQuestId = current.getId(); + PlayerQuests.cancelQuest(player, oldQuestId); - player.getQuestProgresses().remove(oldQuestId); - PlayerQuests.sendPlayerCancelQuestMessage(player, oldQuestId); + player.getQuestProgresses().remove(oldQuestId); + PlayerQuests.sendPlayerCancelQuestMessage(player, current); + } } - player.setDailyQuest(new ValueWithExpiry<>(newQuest, "24h")); - PlayerQuests.beginQuest(player, newQuest); - player.notify("You have a new daily quest! Check your quests menu in the top left to see what you need to do!"); + player.setDailyQuest(new ValueWithExpiry<>(newQuests, RandomQuests.getConfiguration().dailyQuestInterval)); + for(Quest newQuest : newQuests) { + PlayerQuests.beginQuest(player, newQuest); + } + + if(newQuests.size() == 1) { + player.notify("You have a new daily quest! Check your quests menu in the top left to see what you need to do!"); + } else { + player.notify("You have new daily quests! Check your quests menu in the top left to see what you need to do!"); + } } } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java index 30bbc9d1..31de5ed0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java @@ -1,13 +1,13 @@ package brainwine.gameserver.quest; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import brainwine.gameserver.dialog.DialogHelper; import brainwine.gameserver.player.Player; import brainwine.gameserver.server.messages.QuestMessage; -import brainwine.gameserver.util.MapHelper; public class PlayerQuests { private PlayerQuests() {} @@ -93,7 +93,7 @@ public static void cancelQuest(Player player, String questId) { } player.getQuestProgresses().remove(questId); - sendPlayerCancelQuestMessage(player, questId); + sendPlayerCancelQuestMessage(player, progress); } public static void finishQuest(Player player, Quest quest) { @@ -136,10 +136,12 @@ public static void sendInitialPlayerQuestMessages(Player player) { if(progresses == null) return; - Quest dailyQuest = player.getDailyQuest() == null ? null : player.getDailyQuest().getValue(); - if(player.getDailyQuest() != null && !player.getDailyQuest().isExpired() && dailyQuest != null) { - QuestProgress progress = player.getQuestProgresses().get(dailyQuest.getId()); - if(progress != null) sendPlayerQuestMessage(player, progress); + List dailyQuests = player.getDailyQuest() == null ? null : player.getDailyQuest().getValue(); + if(player.getDailyQuest() != null && !player.getDailyQuest().isExpired() && dailyQuests != null) { + for(Quest dailyQuest : dailyQuests) { + QuestProgress progress = player.getQuestProgresses().get(dailyQuest.getId()); + if(progress != null) sendPlayerQuestMessage(player, progress); + } } for(QuestProgress progress : progresses.values()) { @@ -163,8 +165,25 @@ public static void sendPlayerQuestMessage(Player player, QuestProgress progress) } - public static void sendPlayerCancelQuestMessage(Player player, String questId) { - player.sendMessage(new QuestMessage(MapHelper.map("id", questId), null)); + public static void sendPlayerCancelQuestMessage(Player player, Quest current) { + if(player.getQuestProgresses() == null || current == null) return; + QuestProgress progress = player.getQuestProgresses().get(current.getId()); + if(progress != null) sendPlayerCancelQuestMessage(player, progress); + } + + public static void sendPlayerCancelQuestMessage(Player player, QuestProgress progress) { + if(progress == null) return; + Quest quest = progress.getQuest(player); + + Map pcDetails = new HashMap<>(); + pcDetails.put("id", progress.getQuestId()); + pcDetails.put("group", "Cancelled"); + pcDetails.put("title", quest == null || quest.getTitle() == null ? "Cancelled Quest" : quest.getTitle()); + pcDetails.put("xp", 0); + pcDetails.put("desc", quest == null || quest.getDescription() == null ? "This quest has been cancelled. Disconnect and rejoin to make it disappear." : quest.getDescription()); + + progress.getClientStatus(player); + player.sendMessage(new QuestMessage(pcDetails, progress.getClientStatus(player))); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java index 154d31e0..b72dd6fb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java @@ -90,9 +90,12 @@ public static void loadQuests() { public static Quest get(Player player, String questId) { if(player.getDailyQuest() != null - && player.getDailyQuest().getValue() != null - && player.getDailyQuest().getValue().getId().equals(questId)) { - return player.getDailyQuest().getValue(); + && player.getDailyQuest().getValue() != null) { + for(Quest quest : player.getDailyQuest().getValue()) { + if(quest.getId().equals(questId)) { + return quest; + } + } } return get(questId); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java index 3d2f05ef..8b0f287b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java @@ -24,13 +24,17 @@ public class RandomQuests { private static final Logger logger = LogManager.getLogger(); private static Configuration configuration = new Configuration(); - private static class Configuration { + public static class Configuration { @JsonProperty("level_max_tiers") private Map levelMaxTiers = new HashMap<>(); @JsonProperty("strings") Map> strings = new HashMap<>(); @JsonProperty("quests") List randomQuests = new ArrayList<>(); + @JsonProperty("daily_quest_interval") + String dailyQuestInterval = "24h"; + @JsonProperty("daily_quest_count") + int dailyQuestCount = 5; } public static class MapperProvider implements ObjectMapperProvider { @@ -53,6 +57,10 @@ public static void loadConfiguration() { } } + public static Configuration getConfiguration() { + return configuration; + } + public static int getMaxTier(Player player) { if(player == null) return 1; @@ -89,7 +97,6 @@ public static Quest generateRandomPlayerQuest(Random random, Player player) { RandomQuest choice = wm.next(random); Quest quest = new Quest(); - quest.setId("daily_player_quest"); choice.nextQuest(random, player, quest); return quest; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java index 1480c84a..7f76db09 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -52,7 +52,7 @@ public QuestTask nextQuestTask(Random random, CollectItem collectItem) throws Co if(item == null) { itemTitle = itemId; } else { - itemTitle = item.getTitle(); + itemTitle = item.getTitle() == null ? itemId : item.getTitle(); } return itemTitle; }).collect(Collectors.joining(" or ")); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index 5f25877c..ecca0c36 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -55,7 +55,7 @@ public void setTaskDescription(QuestTask task, String taskDescription, String ac List> events = task.getEvents(); List values = events.stream().map(i -> i.get(2)).collect(Collectors.toList()); int quantity = task.getQuantity(); - String times = quantity == 1 ? "" : quantity + " times"; + String times = quantity == 1 ? "" : " " + quantity + " times"; String concat; if(!events.isEmpty() && events.get(0).size() >= 2 && "code".equals(events.get(0).get(1))) { From 4efd39716b6fb5db7a22d3ce6e9e9a573c10badb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 30 Oct 2024 00:31:40 +0100 Subject: [PATCH 09/36] update random-quests.json --- gameserver/src/main/resources/random-quests.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gameserver/src/main/resources/random-quests.json b/gameserver/src/main/resources/random-quests.json index 4a87927d..437a84a6 100644 --- a/gameserver/src/main/resources/random-quests.json +++ b/gameserver/src/main/resources/random-quests.json @@ -1,4 +1,6 @@ { + "daily_quest_interval": "24h", + "daily_quest_count": 5, "strings": { "kill_description": [ "Today you got to kill some critters!", @@ -48,7 +50,7 @@ "items": { "choices": [ { - "items": [ "mechanical/turret" ], + "items": [ "mechanical/turret-pistol" ], "count": { "random_between": [ 3, 5 ] } }, { From 3e85204b70a3e4111a4a4ed2dabeb3c0201dcc83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 13 Nov 2024 22:08:50 +0100 Subject: [PATCH 10/36] reduce repeated types of daily quests --- .../gameserver/quest/DailyQuests.java | 9 ++++----- .../gameserver/quest/RandomQuest.java | 8 +++++++- .../gameserver/quest/RandomQuests.java | 13 +++++-------- .../gameserver/quest/randomquests/Collect.java | 6 +++++- .../gameserver/quest/randomquests/Kill.java | 5 ++++- .../brainwine/gameserver/util/WeightedMap.java | 18 ++++++++++++++++++ 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java index a526e0ba..1be6d677 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java @@ -1,6 +1,5 @@ package brainwine.gameserver.quest; -import java.util.ArrayList; import java.util.List; import brainwine.gameserver.player.Player; @@ -11,13 +10,13 @@ public static void tryIssueDailyQuest(Player player) { ValueWithExpiry> currentV = player.getDailyQuest(); if(currentV == null || currentV.isExpired()) { - List newQuests = new ArrayList<>(); - for(int i = 1; i <= RandomQuests.getConfiguration().dailyQuestCount; i++) { - Quest newQuest = RandomQuests.generateRandomPlayerQuest(player); + List newQuests = RandomQuests.generateRandomPlayerQuests(player, RandomQuests.getConfiguration().dailyQuestCount); + + for(int i = 1; i <= newQuests.size(); i++) { + Quest newQuest = newQuests.get(i - 1); if(newQuest == null) return; newQuest.setId("daily_player_quest_" + i); newQuest.setTitle("Daily Quest #" + i); - newQuests.add(newQuest); } if(currentV.getValue() != null) { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java index 75406788..06e1f323 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java @@ -24,7 +24,13 @@ public abstract class RandomQuest { private int tier = 1; private int frequency = 1; - public abstract void nextQuest(Random random, Player player, Quest quest); + /**Randomly generate a quest according to the specification found in the class instance. + * + * @param random random instance to generate random values with + * @param player player that the quest is being generated for + * @return a quest. Implementors are not obliged to set the title of the quest, but other fields must have valid values + */ + public abstract Quest nextQuest(Random random, Player player); public String getType() { return type; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java index 8b0f287b..e67355d7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java @@ -75,11 +75,11 @@ public static int getMaxTier(Player player) { return tier; } - public static Quest generateRandomPlayerQuest(Player player) { - return generateRandomPlayerQuest(ThreadLocalRandom.current(), player); + public static List generateRandomPlayerQuests(Player player, int n) { + return generateRandomPlayerQuests(ThreadLocalRandom.current(), player, n); } - public static Quest generateRandomPlayerQuest(Random random, Player player) { + public static List generateRandomPlayerQuests(Random random, Player player, int n) { if(configuration.randomQuests.isEmpty()) { return null; } @@ -94,12 +94,9 @@ public static Quest generateRandomPlayerQuest(Random random, Player player) { RandomQuest::getFrequency ); - RandomQuest choice = wm.next(random); + List choices = wm.nextN(random, n); - Quest quest = new Quest(); - choice.nextQuest(random, player, quest); - - return quest; + return choices.stream().map(rq -> rq.nextQuest(random, player)).collect(Collectors.toList()); } public static String getString(Random random, String label) { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java index 7f76db09..a88167a0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -67,8 +67,10 @@ public QuestTask nextQuestTask(Random random, CollectItem collectItem) throws Co } @Override - public void nextQuest(Random random, Player player, Quest quest) { + public Quest nextQuest(Random random, Player player) { try { + Quest quest = new Quest(); + quest.setDescription(RandomQuests.getString(random, "collect_description")); List collectItemList = items.toConcrete(random); List tasks = new ArrayList<>(collectItemList.size()); @@ -77,6 +79,8 @@ public void nextQuest(Random random, Player player, Quest quest) { } quest.setTasks(tasks); if(reward != null) quest.setReward(reward.next(random)); + + return quest; } catch (ConcretionFailureException e) { throw new IllegalStateException("Concretion failure!"); } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index ecca0c36..60c59d28 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -78,8 +78,9 @@ public void setTaskDescription(QuestTask task, String taskDescription, String ac } @Override - public void nextQuest(Random random, Player player, Quest quest) { + public Quest nextQuest(Random random, Player player) { try { + Quest quest = new Quest(); quest.setDescription(RandomQuests.getString(random, "kill_description")); List tasks = new ArrayList<>(); @@ -121,6 +122,8 @@ public void nextQuest(Random random, Player player, Quest quest) { quest.setTasks(tasks); if(reward != null) quest.setReward(reward.next(random)); + + return quest; } catch (ConcretionFailureException e) { throw new IllegalStateException("Concretion failure!"); } diff --git a/gameserver/src/main/java/brainwine/gameserver/util/WeightedMap.java b/gameserver/src/main/java/brainwine/gameserver/util/WeightedMap.java index b99bc808..ff804769 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/WeightedMap.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/WeightedMap.java @@ -1,8 +1,10 @@ package brainwine.gameserver.util; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; @@ -86,6 +88,22 @@ public T next(Random random, T def) { return def; } + + public List nextN(Random random, int n) { + if(entries.isEmpty()) return null; + + List choices = new ArrayList<>(n); + for(int i = 0; i < n; i++) { + T choice = next(random); + choices.add(choice); + double oldWeight = entries.get(choice); + double newWeight = oldWeight * oldWeight / totalWeight; + totalWeight += newWeight - oldWeight; + entries.put(choice, newWeight); + } + + return choices; + } @JsonValue public Map getEntries() { From ec0fe6461bddc4096ace63d7ece80170bd2b461c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 13 Nov 2024 22:30:04 +0100 Subject: [PATCH 11/36] get rid of RandomList.toConcrete --- .../brainwine/gameserver/quest/RandomQuestReward.java | 2 +- .../gameserver/quest/randomquests/Collect.java | 4 ++-- .../brainwine/gameserver/quest/randomquests/Kill.java | 8 ++++---- .../gameserver/util/randomobject/ConcatList.java | 11 ++++++----- .../gameserver/util/randomobject/ConstantList.java | 4 ++-- .../gameserver/util/randomobject/RandomList.java | 8 +------- .../gameserver/util/randomobject/RandomPickList.java | 8 ++++---- 7 files changed, 20 insertions(+), 25 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java index 3e7d12a2..afce0dc2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java @@ -19,7 +19,7 @@ public QuestReward next(Random random) throws ConcretionFailureException { QuestReward result = new QuestReward(); if(xp != null) result.setXp(xp.next(random)); if(crowns != null) result.setCrowns(crowns.next(random)); - if(lootCategories != null) result.setLootCategories(lootCategories.toConcrete(random)); + if(lootCategories != null) result.setLootCategories(lootCategories.next(random)); return result; } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java index a88167a0..bd1ca223 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -38,7 +38,7 @@ public String toString() { public QuestTask nextQuestTask(Random random, CollectItem collectItem) throws ConcretionFailureException { List> events = new ArrayList<>(); - for(String oneItem : collectItem.items.toConcrete(random)) { + for(String oneItem : collectItem.items.next(random)) { events.add(Arrays.asList("collect_item", "id", oneItem, null)); } @@ -72,7 +72,7 @@ public Quest nextQuest(Random random, Player player) { Quest quest = new Quest(); quest.setDescription(RandomQuests.getString(random, "collect_description")); - List collectItemList = items.toConcrete(random); + List collectItemList = items.next(random); List tasks = new ArrayList<>(collectItemList.size()); for(CollectItem c : collectItemList) { tasks.add(nextQuestTask(random, c)); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index 60c59d28..33dc0da7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -84,12 +84,12 @@ public Quest nextQuest(Random random, Player player) { quest.setDescription(RandomQuests.getString(random, "kill_description")); List tasks = new ArrayList<>(); - List actions = this.actions.toConcrete(random); + List actions = this.actions.next(random); String actionMessage = WordUtils.capitalize(String.join(" or ", actions)); if(categories != null) { - List values = categories.toConcrete(random); + List values = categories.next(random); int quantity = defaultIfNull(this.quantity, categoryQuantity).next(random); QuestTask task = new QuestTask() .setEvents(getKillEvents(actions, "category", values)) @@ -99,7 +99,7 @@ public Quest nextQuest(Random random, Player player) { } if(entityIds != null) { - List values = entityIds.toConcrete(random); + List values = entityIds.next(random); int quantity = defaultIfNull(this.quantity, entityIdQuantity).next(random); QuestTask task = new QuestTask() .setEvents(getKillEvents(actions, "entity", values)) @@ -109,7 +109,7 @@ public Quest nextQuest(Random random, Player player) { } if(codes != null) { - List values = codes.toConcrete(random).stream() + List values = codes.next(random).stream() .map(i -> i.next(random)) .collect(Collectors.toList()); int quantity = defaultIfNull(this.quantity, codeQuantity).next(random); diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java index 2378f598..2208e4bf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConcatList.java @@ -13,15 +13,16 @@ public ConcatList(List> concat) { } @Override - public ConstantList next(Random random) throws ConcretionFailureException { - if(concat == null) return new ConstantList<>(); + public List next(Random random) throws ConcretionFailureException { + List result = new ArrayList<>(); + + if(concat == null) return result; - List result = new ArrayList(); for(RandomList l : concat) { - result.addAll(l.next(random).getList()); + result.addAll(l.next(random)); } - return new ConstantList(result); + return result; } @Override diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java index bde3bd3b..9c5e1f8b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/ConstantList.java @@ -17,8 +17,8 @@ public ConstantList(List list) { } @Override - public ConstantList next(Random random) { - return this; + public List next(Random random) { + return list; } public int size() { diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java index d57870c0..65bb1c84 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomList.java @@ -1,14 +1,8 @@ package brainwine.gameserver.util.randomobject; import java.util.List; -import java.util.Random; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @JsonDeserialize(using = RandomListDeserializer.class) -public abstract class RandomList implements Arbitrary> { - public List toConcrete(Random random) throws ConcretionFailureException { - ConstantList finalList = next(random); - return finalList.getList(); - } -} +public abstract class RandomList implements Arbitrary> {} diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java index ce6e1ac3..f2adc70f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java @@ -20,12 +20,12 @@ public RandomPickList(RandomList choices, RandomInteger pick) { } @Override - public ConstantList next(Random random) throws ConcretionFailureException { - ConstantList list = choices.next(random); + public List next(Random random) throws ConcretionFailureException { + List list = choices.next(random); int count = pick.next(random); - List chosen = PickRandom.sampleWithoutReplacement(random, list.getList(), count); + List chosen = PickRandom.sampleWithoutReplacement(random, list, count); - return new ConstantList<>(chosen); + return chosen; } @Override From e9cd79169725f740153c0d09af110ed06abda0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 13 Nov 2024 23:23:01 +0100 Subject: [PATCH 12/36] add craft quest type --- .../gameserver/quest/RandomQuest.java | 7 +++ .../gameserver/quest/RandomQuestReward.java | 18 ++++-- .../gameserver/quest/RandomQuests.java | 5 ++ .../quest/randomquests/Collect.java | 2 - .../gameserver/quest/randomquests/Craft.java | 59 +++++++++++++++++++ .../gameserver/quest/randomquests/Kill.java | 2 - 6 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java index 06e1f323..14f3dd34 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java @@ -5,6 +5,7 @@ import brainwine.gameserver.player.Player; import brainwine.gameserver.quest.randomquests.*; import brainwine.gameserver.util.randomobject.RandomListObjectMapperProvider; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -17,12 +18,18 @@ @JsonSubTypes({ @JsonSubTypes.Type(value = Kill.class, name = "kill"), @JsonSubTypes.Type(value = Collect.class, name = "collect"), + @JsonSubTypes.Type(value = Craft.class, name = "craft"), }) @RandomListObjectMapperProvider(RandomQuests.MapperProvider.class) public abstract class RandomQuest { + @JsonProperty private String type = "none"; + @JsonProperty private int tier = 1; + @JsonProperty private int frequency = 1; + @JsonProperty + protected RandomQuestReward reward = RandomQuests.getDefaultRandomQuestReward(); /**Randomly generate a quest according to the specification found in the class instance. * diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java index afce0dc2..c63804fb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java @@ -3,17 +3,27 @@ import java.util.Random; import brainwine.gameserver.util.randomobject.*; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonInclude(JsonInclude.Include.NON_NULL) public class RandomQuestReward implements Arbitrary { @JsonProperty("xp") - RandomInteger xp = null; + private RandomInteger xp = null; @JsonProperty("crowns") - RandomInteger crowns = null; + private RandomInteger crowns = null; @JsonProperty("loot_categories") @RandomListItemType(String.class) - RandomList lootCategories = null; - + private RandomList lootCategories = null; + + public RandomQuestReward() {} + + public RandomQuestReward(RandomInteger xp, RandomInteger crowns, RandomList lootCategories) { + this.xp = xp; + this.crowns = crowns; + this.lootCategories = lootCategories; + } + @Override public QuestReward next(Random random) throws ConcretionFailureException { QuestReward result = new QuestReward(); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java index e67355d7..41c65118 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java @@ -8,6 +8,7 @@ import java.util.stream.Collectors; import brainwine.gameserver.util.randomobject.ObjectMapperProvider; +import brainwine.gameserver.util.randomobject.RandomInteger; import brainwine.shared.JsonHelper; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; @@ -99,6 +100,10 @@ public static List generateRandomPlayerQuests(Random random, Player playe return choices.stream().map(rq -> rq.nextQuest(random, player)).collect(Collectors.toList()); } + public static RandomQuestReward getDefaultRandomQuestReward() { + return new RandomQuestReward(new RandomInteger(100), new RandomInteger(0), null); + } + public static String getString(Random random, String label) { List list = configuration.strings.get(label); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java index bd1ca223..0eb83a3e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -13,8 +13,6 @@ public class Collect extends RandomQuest { @JsonProperty("items") @RandomListItemType(CollectItem.class) private RandomList items; - @JsonProperty("reward") - private RandomQuestReward reward = null; @JsonProperty("task_description") private String taskDescription = null; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java new file mode 100644 index 00000000..39478e4c --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java @@ -0,0 +1,59 @@ +package brainwine.gameserver.quest.randomquests; + +import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.quest.*; +import brainwine.gameserver.util.randomobject.ConcretionFailureException; +import brainwine.gameserver.util.randomobject.RandomInteger; +import brainwine.gameserver.util.randomobject.RandomList; +import brainwine.gameserver.util.randomobject.RandomListItemType; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +public class Craft extends RandomQuest { + @JsonProperty("items") + @RandomListItemType(CraftItem.class) + private RandomList items; + + private static class CraftItem { + @JsonProperty + String item = Item.AIR.getId(); + @JsonProperty + RandomInteger count = new RandomInteger(1); + } + + @Override + public Quest nextQuest(Random random, Player player) { + try { + Quest quest = new Quest(); + quest.setDescription(RandomQuests.getString(random, "craft_description")); + + List chosenItems = items.next(random); + + List tasks = new ArrayList<>(chosenItems.size()); + for(CraftItem craftItem : chosenItems) { + Item item = Item.get(craftItem.item); + + String itemName = item == null || item.getTitle() == null ? craftItem.item : item.getTitle(); + int amount = craftItem.count.next(random); + QuestTask task = new QuestTask(); + task.setDescription("Craft " + amount + " of " + itemName); + task.setEvents(Arrays.asList( + Arrays.asList("craft", "code", item.getCode()) + )); + tasks.add(task); + } + quest.setTasks(tasks); + + if(reward != null) quest.setReward(reward.next(random)); + + return quest; + } catch (ConcretionFailureException e) { + throw new IllegalStateException("Concretion failure!"); + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index 33dc0da7..b0c22bc8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -33,8 +33,6 @@ public class Kill extends RandomQuest { private RandomInteger entityIdQuantity = new RandomInteger(1); @JsonProperty("task_description") private String taskDescription = null; - @JsonProperty("reward") - private RandomQuestReward reward = null; @JsonProperty("actions") @RandomListItemType(String.class) private RandomList actions = new ConstantList<>(Arrays.asList("kill")); From 7f16662368d22f5e9e1a40b5f64f6d086860f710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 13 Nov 2024 23:24:22 +0100 Subject: [PATCH 13/36] update random-quests.json --- gameserver/src/main/resources/random-quests.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gameserver/src/main/resources/random-quests.json b/gameserver/src/main/resources/random-quests.json index 437a84a6..370280e3 100644 --- a/gameserver/src/main/resources/random-quests.json +++ b/gameserver/src/main/resources/random-quests.json @@ -13,6 +13,12 @@ "The shelves in your father's thrift store fell empty. Help them stock it back up!", "You had been scammed by Graptik, but don't be afraid. Today is the day you will get your items back.", "Hot stuff in your area, waiting for you to collect, within the next 24 hours@" + ], + "craft_description": [ + "Today you are tasked to craft the following items.", + "Time to be productive.", + "Craft these.", + "The mayor of Cake Land needs you to craft these items!" ] }, "quests": [ @@ -61,6 +67,14 @@ "pick": 1 }, "reward": { "xp": 100, "crowns": 2 } + }, + { + "frequency": 10, + "type": "craft", + "items": { "choices": [ + { "item": "building/door-wood", "count": { "random_between": [ 3, 5 ]}}, + { "item": "building/door-brass", "count": { "random_between": [ 3, 5 ]}} + ]} } ] } From f43307010ef63e5f22598018de143774cb5ab6a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 14 Nov 2024 19:36:05 +0100 Subject: [PATCH 14/36] refactor various things and fix bugs --- .../gameserver/quest/QuestEvents.java | 23 ++-- .../quest/randomquests/Collect.java | 2 +- .../gameserver/quest/randomquests/Craft.java | 3 +- .../gameserver/quest/randomquests/Kill.java | 115 +++++++++++------- .../server/requests/CraftRequest.java | 2 + 5 files changed, 90 insertions(+), 55 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java index 77682f8f..59295988 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java @@ -64,6 +64,10 @@ private static boolean patternMatch(List event, Object[] pattern) { } public static void handleEvent(Player player, Object... pattern) { + handleEventWithQuantity(player, 1, pattern); + } + + public static void handleEventWithQuantity(Player player, int quantity, Object... pattern) { for(Map.Entry questProgressEntry : player.getQuestProgresses().entrySet()) { String questId = questProgressEntry.getKey(); QuestProgress questProgress = questProgressEntry.getValue(); @@ -81,16 +85,7 @@ public static void handleEvent(Player player, Object... pattern) { for(List event : task.getEvents()) { try { if(patternMatch(event, pattern)) { - if(event.size() >= 4 && "collect_item".equals(event.get(0)) && "id".equals(event.get(1))) { - Integer amount = (Integer) pattern[3]; - if(amount != null && amount > 0) { - questProgress.getTaskProgresses().set(i, questProgress.getTaskProgress(i) + amount); - } - anyProgress = true; - break; - } - - questProgress.getTaskProgresses().set(i, questProgress.getTaskProgress(i) + 1); + questProgress.getTaskProgresses().set(i, questProgress.getTaskProgress(i) + quantity); if(event.size() >= 3 && "interact".equals(event.get(0)) && "name".equals(event.get(1))) { PlayerQuests.performAction(player, quest, QuestAction.Type.INTERACT); @@ -119,7 +114,11 @@ public static void handleEvent(Player player, Object... pattern) { } public static void handleCollectItem(Player player, Item item, int quantity) { - handleEvent(player, "collect_item", "id", item.getId(), quantity); + handleEventWithQuantity(player, quantity, "collect_item", "id", item.getId(), quantity); + } + + public static void handleCraft(Player player, Item item, int quantity) { + handleEventWithQuantity(player, quantity, "craft", "code", item.getCode()); } public static void handleEnterZone(Player player, Zone zone) { @@ -160,7 +159,7 @@ public static void handleInteract(Player player, Npc npc) { } public static void handleAppearance(Player player, Map appearance) { - for(Object code : appearance.values()) { + for (Object code : appearance.values()) { handleEvent(player, "appearance", "code", code); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java index 0eb83a3e..5d2c876c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -37,7 +37,7 @@ public QuestTask nextQuestTask(Random random, CollectItem collectItem) throws Co List> events = new ArrayList<>(); for(String oneItem : collectItem.items.next(random)) { - events.add(Arrays.asList("collect_item", "id", oneItem, null)); + events.add(Arrays.asList("collect_item", "id", oneItem)); } int quantity = collectItem.count.next(random); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java index 39478e4c..c8593842 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java @@ -43,8 +43,9 @@ public Quest nextQuest(Random random, Player player) { QuestTask task = new QuestTask(); task.setDescription("Craft " + amount + " of " + itemName); task.setEvents(Arrays.asList( - Arrays.asList("craft", "code", item.getCode()) + Arrays.asList("craft", "code", item == null ? 0 : item.getCode()) )); + task.setQuantity(amount); tasks.add(task); } quest.setTasks(tasks); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index b0c22bc8..0496fc26 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -5,7 +5,10 @@ import java.util.List; import java.util.Random; import java.util.stream.Collectors; +import java.util.stream.Stream; +import brainwine.gameserver.entity.EntityConfig; +import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.player.Player; import brainwine.gameserver.quest.*; import brainwine.gameserver.util.randomobject.*; @@ -48,31 +51,55 @@ private List> getKillEvents(List actions, String type, List return events; } - public void setTaskDescription(QuestTask task, String taskDescription, String actionMessage) { + private String joinWithOr(List items) { + if(items == null || items.size() == 0) return "nothing"; + if(items.size() == 1) return items.get(0); + if(items.size() == 2) return items.get(0) + " or " + items.get(1); + else return String.join(", ", items.subList(0, items.size() - 1)) + ", or " + items.get(items.size() - 1); + } + + private QuestTask makeTaskForEntityTypes(List actions, List names, List codes, int quantity) { + List> events = getKillEvents(actions, "code", codes); + + String message; if(taskDescription == null) { - List> events = task.getEvents(); - List values = events.stream().map(i -> i.get(2)).collect(Collectors.toList()); - int quantity = task.getQuantity(); + String actionMessage = WordUtils.capitalize(joinWithOr(actions)); String times = quantity == 1 ? "" : " " + quantity + " times"; + String concat = joinWithOr(names); - String concat; - if(!events.isEmpty() && events.get(0).size() >= 2 && "code".equals(events.get(0).get(1))) { - concat = String.join(", ", values.stream().map(v -> "(Type: " + v + ")").collect(Collectors.toList())); - } else { - concat = String.join(", ", values.stream().map(v -> (String) v).collect(Collectors.toList())); - } + String beginning; + if(names.size() == 1) beginning = Stream.of("a", "e", "i", "o", "u").anyMatch(concat::startsWith) ? " an " : " a "; + else beginning = " any of "; - String message; - if(values.size() == 1) { - message = actionMessage + " an entity " + concat + times; - } else { - message = actionMessage + " any of the entities " + concat + times; - } + message = actionMessage + beginning + concat + times; + } else { + message = taskDescription.replaceAll("\\{QUANTITY\\}", Integer.toString(quantity)); + } + + return new QuestTask().setDescription(message).setEvents(events).setQuantity(quantity); + } + + private QuestTask makeTaskForEntityCategories(List actions, List categories, int quantity) { + List> events = getKillEvents(actions, "category", categories); - task.setDescription(message); + String message; + if(taskDescription == null) { + String actionMessage = WordUtils.capitalize(String.join(" or ", actions)); + + String times = quantity == 1 ? "" : " " + quantity + " times"; + + String concat = joinWithOr(categories); + + String beginning; + if(categories.size() == 1) beginning = " an entities of category "; + else beginning = " an entity of categories "; + + message = actionMessage + beginning + concat + times; } else { - task.setDescription(taskDescription.replaceAll("\\{QUANTITY\\}", Integer.toString(task.getQuantity()))); + message = taskDescription.replaceAll("\\{QUANTITY\\}", Integer.toString(quantity)); } + + return new QuestTask().setDescription(message).setEvents(events).setQuantity(quantity); } @Override @@ -84,38 +111,44 @@ public Quest nextQuest(Random random, Player player) { List tasks = new ArrayList<>(); List actions = this.actions.next(random); - String actionMessage = WordUtils.capitalize(String.join(" or ", actions)); - if(categories != null) { List values = categories.next(random); - int quantity = defaultIfNull(this.quantity, categoryQuantity).next(random); - QuestTask task = new QuestTask() - .setEvents(getKillEvents(actions, "category", values)) - .setQuantity(quantity); - setTaskDescription(task, taskDescription, actionMessage); - tasks.add(task); + int quantity = defaultIfNull(categoryQuantity, this.quantity).next(random); + tasks.add(makeTaskForEntityCategories(actions, values, quantity)); } if(entityIds != null) { List values = entityIds.next(random); - int quantity = defaultIfNull(this.quantity, entityIdQuantity).next(random); - QuestTask task = new QuestTask() - .setEvents(getKillEvents(actions, "entity", values)) - .setQuantity(quantity); - setTaskDescription(task, taskDescription, actionMessage); - tasks.add(task); + int quantity = defaultIfNull(entityIdQuantity, this.quantity).next(random); + List names = new ArrayList<>(); + List codes = new ArrayList<>(); + for(String id : values) { + EntityConfig config = EntityRegistry.getEntityConfig(id); + + if(config == null) { + names.add(id); + codes.add(0); + continue; + } + + names.add(config.getName()); + codes.add(config.getType()); + } + + tasks.add(makeTaskForEntityTypes(actions, names, codes, quantity)); } if(codes != null) { - List values = codes.next(random).stream() - .map(i -> i.next(random)) - .collect(Collectors.toList()); - int quantity = defaultIfNull(this.quantity, codeQuantity).next(random); - QuestTask task = new QuestTask() - .setEvents(getKillEvents(actions, "code", values)) - .setQuantity(quantity); - setTaskDescription(task, taskDescription, actionMessage); - tasks.add(task); + List values = codes.next(random).stream().map(ri -> ri.next(random)).collect(Collectors.toList()); + int quantity = defaultIfNull(codeQuantity, this.quantity).next(random); + List names = new ArrayList<>(); + List codes = new ArrayList<>(); + for(Integer code : values) { + names.add("(Type: " + code + ")"); + codes.add(code); + } + + tasks.add(makeTaskForEntityTypes(actions, names, codes, quantity)); } quest.setTasks(tasks); diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java index df734c36..a037c61c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java @@ -8,6 +8,7 @@ import brainwine.gameserver.player.Inventory; import brainwine.gameserver.player.Player; import brainwine.gameserver.player.Skill; +import brainwine.gameserver.quest.QuestEvents; import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.RequestInfo; @@ -100,5 +101,6 @@ public void process(Player player) { int totalQuantity = item.getCraftingQuantity() * quantity; inventory.addItem(item, totalQuantity); player.getStatistics().trackItemCrafted(item, totalQuantity); + QuestEvents.handleCraft(player, item, totalQuantity); } } From 6a738966dcb9d0bb1b692dfafff04ecd50c9ec60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 14 Nov 2024 20:51:06 +0100 Subject: [PATCH 15/36] add multistep random quests --- .../gameserver/quest/RandomQuest.java | 6 +- .../gameserver/quest/RandomQuestReward.java | 8 +++ .../gameserver/quest/RandomQuests.java | 5 +- .../quest/randomquests/Collect.java | 4 +- .../gameserver/quest/randomquests/Craft.java | 2 +- .../gameserver/quest/randomquests/Kill.java | 6 +- .../quest/randomquests/MultiStep.java | 64 +++++++++++++++++++ 7 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/quest/randomquests/MultiStep.java diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java index 14f3dd34..028bbca2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java @@ -16,6 +16,7 @@ visible = true ) @JsonSubTypes({ + @JsonSubTypes.Type(value = MultiStep.class, name = "multistep"), @JsonSubTypes.Type(value = Kill.class, name = "kill"), @JsonSubTypes.Type(value = Collect.class, name = "collect"), @JsonSubTypes.Type(value = Craft.class, name = "craft"), @@ -29,7 +30,7 @@ public abstract class RandomQuest { @JsonProperty private int frequency = 1; @JsonProperty - protected RandomQuestReward reward = RandomQuests.getDefaultRandomQuestReward(); + private RandomQuestReward reward = null; /**Randomly generate a quest according to the specification found in the class instance. * @@ -51,4 +52,7 @@ public int getFrequency() { return frequency; } + public RandomQuestReward getReward() { + return reward; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java index c63804fb..d8d90496 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuestReward.java @@ -33,4 +33,12 @@ public QuestReward next(Random random) throws ConcretionFailureException { return result; } + + public static QuestReward nextOrDefault(Random random, RandomQuestReward... options) throws ConcretionFailureException { + for(RandomQuestReward r : options) { + if(r != null) return r.next(random); + } + + return new QuestReward().setXp(100); + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java index 41c65118..7d10933c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuests.java @@ -7,6 +7,7 @@ import java.util.*; import java.util.stream.Collectors; +import brainwine.gameserver.util.randomobject.ConcretionFailureException; import brainwine.gameserver.util.randomobject.ObjectMapperProvider; import brainwine.gameserver.util.randomobject.RandomInteger; import brainwine.shared.JsonHelper; @@ -100,10 +101,6 @@ public static List generateRandomPlayerQuests(Random random, Player playe return choices.stream().map(rq -> rq.nextQuest(random, player)).collect(Collectors.toList()); } - public static RandomQuestReward getDefaultRandomQuestReward() { - return new RandomQuestReward(new RandomInteger(100), new RandomInteger(0), null); - } - public static String getString(Random random, String label) { List list = configuration.strings.get(label); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java index 5d2c876c..86aa9cbe 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -76,7 +76,7 @@ public Quest nextQuest(Random random, Player player) { tasks.add(nextQuestTask(random, c)); } quest.setTasks(tasks); - if(reward != null) quest.setReward(reward.next(random)); + quest.setReward(RandomQuestReward.nextOrDefault(random, getReward())); return quest; } catch (ConcretionFailureException e) { @@ -88,7 +88,7 @@ public Quest nextQuest(Random random, Player player) { public String toString() { return "Collect{" + "items=" + items + - ", reward=" + reward + + ", reward=" + getReward() + ", taskDescription='" + taskDescription + '\'' + '}'; } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java index c8593842..56e0f58b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java @@ -50,7 +50,7 @@ public Quest nextQuest(Random random, Player player) { } quest.setTasks(tasks); - if(reward != null) quest.setReward(reward.next(random)); + quest.setReward(RandomQuestReward.nextOrDefault(random, getReward())); return quest; } catch (ConcretionFailureException e) { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index 0496fc26..302e0e3e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -131,7 +131,7 @@ public Quest nextQuest(Random random, Player player) { continue; } - names.add(config.getName()); + names.add(defaultIfNull(config.getTitle(), id)); codes.add(config.getType()); } @@ -152,7 +152,7 @@ public Quest nextQuest(Random random, Player player) { } quest.setTasks(tasks); - if(reward != null) quest.setReward(reward.next(random)); + quest.setReward(RandomQuestReward.nextOrDefault(random, getReward())); return quest; } catch (ConcretionFailureException e) { @@ -166,7 +166,7 @@ public String toString() { sb.append("Kill{ "); sb.append("taskDescription='" + taskDescription + '\'' + - ", reward=" + reward + + ", reward=" + getReward() + ", actions=" + actions); if(categories != null) { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/MultiStep.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/MultiStep.java new file mode 100644 index 00000000..5e1334fe --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/MultiStep.java @@ -0,0 +1,64 @@ +package brainwine.gameserver.quest.randomquests; + +import brainwine.gameserver.player.Player; +import brainwine.gameserver.quest.Quest; +import brainwine.gameserver.quest.QuestTask; +import brainwine.gameserver.quest.RandomQuest; +import brainwine.gameserver.quest.RandomQuestReward; +import brainwine.gameserver.util.randomobject.ConcretionFailureException; +import brainwine.gameserver.util.randomobject.ConstantList; +import brainwine.gameserver.util.randomobject.RandomList; +import brainwine.gameserver.util.randomobject.RandomListItemType; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +public class MultiStep extends RandomQuest { + @JsonProperty + @RandomListItemType(RandomQuest.class) + private RandomList steps = new ConstantList<>(); + @JsonProperty + private String description; + + @Override + public Quest nextQuest(Random random, Player player) { + try { + List chosen = steps.next(random); + List quests = chosen.stream() + .map(rq -> rq.nextQuest(random, player)) + .collect(Collectors.toList()); + + List tasks = new ArrayList<>(); + + for(Quest quest : quests) { + tasks.addAll(quest.getTasks()); + } + + Quest result = new Quest(); + + if(!quests.isEmpty()) { + Quest toBeCopied = quests.get(quests.size() - 1); + RandomQuestReward altRandomQuestReward = chosen.get(quests.size() - 1).getReward(); + result.setDescription(defaultIfNull(description, toBeCopied.getDescription())); + + return new Quest() + .setDescription(defaultIfNull(description, toBeCopied.getDescription())) + .setTasks(tasks) + .setReward(RandomQuestReward.nextOrDefault(random, getReward(), altRandomQuestReward)); + } + + return new Quest() + .setDescription(description) + .setTasks(tasks) + .setReward(RandomQuestReward.nextOrDefault(random, getReward())); + + } catch(ConcretionFailureException e) { + throw new IllegalStateException("Concretion failure!"); + } + } +} From 8e8f25e9b68a0432f9fa6c95d523ef5407f03bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 14 Nov 2024 20:52:13 +0100 Subject: [PATCH 16/36] add more random quest config examples --- .../src/main/resources/random-quests.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/gameserver/src/main/resources/random-quests.json b/gameserver/src/main/resources/random-quests.json index 370280e3..782f6ee7 100644 --- a/gameserver/src/main/resources/random-quests.json +++ b/gameserver/src/main/resources/random-quests.json @@ -50,6 +50,12 @@ "crowns": 2 } }, + { + "frequency": 10, + "type": "kill", + "entities": { "choices": ["creatures/crow-auto", "creatures/crow"] }, + "quantity": 1 + }, { "frequency": 10, "type": "collect", @@ -75,6 +81,17 @@ { "item": "building/door-wood", "count": { "random_between": [ 3, 5 ]}}, { "item": "building/door-brass", "count": { "random_between": [ 3, 5 ]}} ]} + }, + { + "frequency": 10, + "type": "multistep", + "reward": { "crowns": 3 }, + "steps": [ + { "type": "collect", "items": [{ "items": ["building/wood"], "count": 3 }] }, + { "type": "collect", "items": [{ "items": ["building/brass"], "count": 6 }] }, + { "type": "collect", "items": [{ "items": ["building/iron"], "count": 3 }] }, + { "type": "craft", "items": [{ "item": "building/door-wood", "count": 3 }, { "item": "building/door-brass", "count": 3 }] } + ] } ] } From 518e39a3b8da2e3871f7d766522d9d0edeb9fe12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sun, 17 Nov 2024 03:01:09 +0100 Subject: [PATCH 17/36] code quality --- .../gameserver/quest/QuestEvents.java | 43 ------------------- .../util/randomobject/RandomPickList.java | 4 +- 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java index 59295988..f3f3d950 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java @@ -11,45 +11,6 @@ import brainwine.gameserver.zone.Zone; public class QuestEvents { - public static void handleQuestFinalReturn(Player player, Quest quest) { - QuestProgress progress = player.getQuestProgresses().get(quest.getId()); - - if(progress == null) return; - - int found = -1; - - for(int i = 0; i < quest.getTasks().size(); i++) { - QuestTask task = quest.getTasks().get(i); - - if(task.getEvents() != null) { - for(List event : task.getEvents()) { - for(Object o : event) { - if("return".equals(o)) found = i; - if(found != -1) break; - } - if(found != -1) break; - } - } - if(found != -1) break; - } - - if(found == -1) return; - - int initialReturnProgress = progress.getTaskProgress(found); - int wantedProgress = quest.getTasks().get(found).getQuantity(); - - while (progress.getTaskProgresses().size() < quest.getTasks().size()) { - progress.getTaskProgresses().add(0); - } - - progress.getTaskProgresses().set(found, wantedProgress); - - // if can't finish even with the return task done, set the return task progress to the previous value - if(!PlayerQuests.canFinishQuest(player, quest)) { - progress.getTaskProgresses().set(found, initialReturnProgress); - } - } - private static boolean patternMatch(List event, Object[] pattern) { if(event.size() != pattern.length) return false; int iterationCount = Math.min(event.size(), pattern.length); @@ -154,10 +115,6 @@ public static void handleChat(Player player) { handleEvent(player, "chat"); } - public static void handleInteract(Player player, Npc npc) { - handleEvent(player, "interact", "name", npc.getName()); - } - public static void handleAppearance(Player player, Map appearance) { for (Object code : appearance.values()) { handleEvent(player, "appearance", "code", code); diff --git a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java index f2adc70f..f263218f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/randomobject/RandomPickList.java @@ -23,9 +23,7 @@ public RandomPickList(RandomList choices, RandomInteger pick) { public List next(Random random) throws ConcretionFailureException { List list = choices.next(random); int count = pick.next(random); - List chosen = PickRandom.sampleWithoutReplacement(random, list, count); - - return chosen; + return PickRandom.sampleWithoutReplacement(random, list, count); } @Override From 26cfb3cbef0637372cddb084b99109086cf51f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 19 Nov 2024 02:47:06 +0100 Subject: [PATCH 18/36] language --- .../src/main/java/brainwine/gameserver/quest/DailyQuests.java | 1 + .../main/java/brainwine/gameserver/quest/randomquests/Kill.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java index 1be6d677..20c89b79 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java @@ -17,6 +17,7 @@ public static void tryIssueDailyQuest(Player player) { if(newQuest == null) return; newQuest.setId("daily_player_quest_" + i); newQuest.setTitle("Daily Quest #" + i); + newQuest.setGroup("Daily Quests"); } if(currentV.getValue() != null) { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index 302e0e3e..1c479f2b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -91,7 +91,7 @@ private QuestTask makeTaskForEntityCategories(List actions, List String concat = joinWithOr(categories); String beginning; - if(categories.size() == 1) beginning = " an entities of category "; + if(categories.size() == 1) beginning = " an entity of category "; else beginning = " an entity of categories "; message = actionMessage + beginning + concat + times; From 0dcaefcb9a86d2736887dee3e4ede3d1512279f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 19 Nov 2024 03:05:43 +0100 Subject: [PATCH 19/36] remove setters for state variables --- .../main/java/brainwine/gameserver/quest/Quest.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java index e546aa78..a9aac8cd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java @@ -174,15 +174,5 @@ public Quest setTasks(List tasks) { this.tasks = tasks; return this; } - - public Quest setPcDetails(Map pcDetails) { - this.pcDetails = pcDetails; - return this; - } - - public Quest setMobileDetails(Map mobileDetails) { - this.mobileDetails = mobileDetails; - return this; - } } From 132fdc6ffed864d32a6aacdaab27bfc2287baf78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 20 Nov 2024 20:25:20 +0100 Subject: [PATCH 20/36] fix some null errors Closes #15 --- .../main/java/brainwine/gameserver/quest/QuestReward.java | 6 +++--- .../src/main/java/brainwine/gameserver/quest/Quests.java | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java index baff0159..2fc470bf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java @@ -17,11 +17,11 @@ public class QuestReward { private List lootCategories; public void reward(Player player) { - if(xp != null) { + if(crowns != null) { player.addCrowns(crowns); } - if(crowns != null) { - player.addExperience(xp, String.format("You have gained %d XP from completing this quest!", xp, crowns)); + if(xp != null) { + player.addExperience(xp, String.format("You have gained %d XP from completing this quest!", xp)); } if(lootCategories != null) { Loot loot = GameServer.getInstance().getLootManager().getRandomLoot(player, lootCategories); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java index b72dd6fb..e0136278 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quests.java @@ -125,6 +125,11 @@ public static Quest get(String questId) { public static List getRandomQuestsFromCategory(Entity me, String categoryTitle, Set excludeQuestIds, int count) { List targetList = questLists.get(categoryTitle); + + if(targetList == null) { + return null; + } + targetList = targetList.stream().filter(q -> !excludeQuestIds.contains(q.getId())).collect(Collectors.toList()); if(targetList == null) { From 8529d4ba2e7196773ca0510c2c592bf83c68fc0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sat, 30 Nov 2024 00:56:27 +0100 Subject: [PATCH 21/36] fix collect item quantity bug --- .../src/main/java/brainwine/gameserver/quest/QuestEvents.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java index f3f3d950..07cee6e6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java @@ -75,7 +75,7 @@ public static void handleEventWithQuantity(Player player, int quantity, Object.. } public static void handleCollectItem(Player player, Item item, int quantity) { - handleEventWithQuantity(player, quantity, "collect_item", "id", item.getId(), quantity); + handleEventWithQuantity(player, quantity, "collect_item", "id", item.getId()); } public static void handleCraft(Player player, Item item, int quantity) { From 1ef7e1f1064e52d54dbf83b92f676c85bf8b9df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 5 Dec 2024 00:07:17 +0100 Subject: [PATCH 22/36] make some changes --- .../brainwine/gameserver/quest/Quest.java | 2 + .../gameserver/quest/QuestAction.java | 6 ++- .../gameserver/quest/QuestReward.java | 8 ++-- .../gameserver/quest/QuestStory.java | 2 + .../gameserver/quest/RandomQuest.java | 8 ++++ .../quest/randomquests/Collect.java | 25 ++--------- .../gameserver/quest/randomquests/Craft.java | 42 +++++++++++-------- .../gameserver/quest/randomquests/Kill.java | 9 +--- 8 files changed, 49 insertions(+), 53 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java index a9aac8cd..c1fbd63a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java @@ -7,12 +7,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import brainwine.gameserver.GameConfiguration; import brainwine.gameserver.util.MapHelper; @JsonIgnoreProperties("zones") +@JsonInclude(JsonInclude.Include.NON_NULL) public class Quest { private String id; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestAction.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestAction.java index 69d8e115..7748423a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestAction.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestAction.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -17,14 +18,15 @@ import brainwine.gameserver.server.messages.InventoryMessage; import brainwine.shared.JsonHelper; +@JsonInclude(JsonInclude.Include.NON_NULL) public class QuestAction { - public static enum Type { + public enum Type { INTERACT, BEGIN, DONE; } - public static enum Actor { + public enum Actor { PLAYER, ANDROID; } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java index 2fc470bf..bf74934e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java @@ -10,17 +10,17 @@ public class QuestReward { @JsonProperty(required = false) - private Integer xp = null; + private int xp = 0; @JsonProperty(required = false) - private Integer crowns = null; + private int crowns = 0; @JsonProperty(value = "loot_categories", required = false) private List lootCategories; public void reward(Player player) { - if(crowns != null) { + if(crowns != 0) { player.addCrowns(crowns); } - if(xp != null) { + if(xp != 0) { player.addExperience(xp, String.format("You have gained %d XP from completing this quest!", xp)); } if(lootCategories != null) { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestStory.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestStory.java index b592cbd1..0da3b032 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestStory.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestStory.java @@ -1,9 +1,11 @@ package brainwine.gameserver.quest; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import brainwine.gameserver.player.Player; +@JsonInclude(JsonInclude.Include.NON_NULL) public class QuestStory { private String intro; private String accept; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java index 028bbca2..c540fb3e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/RandomQuest.java @@ -1,5 +1,6 @@ package brainwine.gameserver.quest; +import java.util.List; import java.util.Random; import brainwine.gameserver.player.Player; @@ -32,6 +33,13 @@ public abstract class RandomQuest { @JsonProperty private RandomQuestReward reward = null; + protected String joinWithOr(List items) { + if(items == null || items.size() == 0) return "nothing"; + if(items.size() == 1) return items.get(0); + if(items.size() == 2) return items.get(0) + " or " + items.get(1); + else return String.join(", ", items.subList(0, items.size() - 1)) + ", or " + items.get(items.size() - 1); + } + /**Randomly generate a quest according to the specification found in the class instance. * * @param random random instance to generate random values with diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java index 86aa9cbe..89eaf892 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -10,9 +10,9 @@ import java.util.stream.Collectors; public class Collect extends RandomQuest { - @JsonProperty("items") + @JsonProperty("tasks") @RandomListItemType(CollectItem.class) - private RandomList items; + private RandomList tasks; @JsonProperty("task_description") private String taskDescription = null; @@ -23,17 +23,9 @@ private static class CollectItem { RandomList items = new ConstantList<>(); @JsonProperty("count") RandomInteger count = new RandomInteger(1); - - @Override - public String toString() { - return "CollectItem{" + - "items=" + items + - ", count=" + count + - '}'; - } } - public QuestTask nextQuestTask(Random random, CollectItem collectItem) throws ConcretionFailureException { + private QuestTask nextQuestTask(Random random, CollectItem collectItem) throws ConcretionFailureException { List> events = new ArrayList<>(); for(String oneItem : collectItem.items.next(random)) { @@ -70,7 +62,7 @@ public Quest nextQuest(Random random, Player player) { Quest quest = new Quest(); quest.setDescription(RandomQuests.getString(random, "collect_description")); - List collectItemList = items.next(random); + List collectItemList = tasks.next(random); List tasks = new ArrayList<>(collectItemList.size()); for(CollectItem c : collectItemList) { tasks.add(nextQuestTask(random, c)); @@ -83,13 +75,4 @@ public Quest nextQuest(Random random, Player player) { throw new IllegalStateException("Concretion failure!"); } } - - @Override - public String toString() { - return "Collect{" + - "items=" + items + - ", reward=" + getReward() + - ", taskDescription='" + taskDescription + '\'' + - '}'; - } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java index 56e0f58b..cae2e7d8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java @@ -3,10 +3,7 @@ import brainwine.gameserver.item.Item; import brainwine.gameserver.player.Player; import brainwine.gameserver.quest.*; -import brainwine.gameserver.util.randomobject.ConcretionFailureException; -import brainwine.gameserver.util.randomobject.RandomInteger; -import brainwine.gameserver.util.randomobject.RandomList; -import brainwine.gameserver.util.randomobject.RandomListItemType; +import brainwine.gameserver.util.randomobject.*; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; @@ -15,14 +12,17 @@ import java.util.Random; public class Craft extends RandomQuest { - @JsonProperty("items") + @JsonProperty("tasks") @RandomListItemType(CraftItem.class) - private RandomList items; + private RandomList tasks; + @JsonProperty("task_description") + private String taskDescription = null; private static class CraftItem { - @JsonProperty - String item = Item.AIR.getId(); - @JsonProperty + @JsonProperty("items") + @RandomListItemType(String.class) + RandomList items = new ConstantList<>(); + @JsonProperty("count") RandomInteger count = new RandomInteger(1); } @@ -32,24 +32,30 @@ public Quest nextQuest(Random random, Player player) { Quest quest = new Quest(); quest.setDescription(RandomQuests.getString(random, "craft_description")); - List chosenItems = items.next(random); + List chosenItems = tasks.next(random); List tasks = new ArrayList<>(chosenItems.size()); for(CraftItem craftItem : chosenItems) { - Item item = Item.get(craftItem.item); - - String itemName = item == null || item.getTitle() == null ? craftItem.item : item.getTitle(); + List itemsToCraft = craftItem.items.next(random); int amount = craftItem.count.next(random); + List> events = new ArrayList<>(itemsToCraft.size()); + List itemNames = new ArrayList<>(itemsToCraft.size()); + + for(String itemId : itemsToCraft) { + Item item = Item.get(itemId); + + itemNames.add(item == null || item.getTitle() == null ? itemId : item.getTitle()); + events.add(Arrays.asList("craft", "code", item == null ? 0 : item.getCode())); + } + QuestTask task = new QuestTask(); - task.setDescription("Craft " + amount + " of " + itemName); - task.setEvents(Arrays.asList( - Arrays.asList("craft", "code", item == null ? 0 : item.getCode()) - )); + task.setDescription("Craft " + amount + " of " + joinWithOr(itemNames)); + task.setEvents(events); task.setQuantity(amount); + tasks.add(task); } quest.setTasks(tasks); - quest.setReward(RandomQuestReward.nextOrDefault(random, getReward())); return quest; diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index 1c479f2b..49f82cf3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -38,7 +38,7 @@ public class Kill extends RandomQuest { private String taskDescription = null; @JsonProperty("actions") @RandomListItemType(String.class) - private RandomList actions = new ConstantList<>(Arrays.asList("kill")); + private RandomList actions = new ConstantList<>(Arrays.asList("kill", "explode")); private List> getKillEvents(List actions, String type, List values) { List> events = new ArrayList<>(actions.size() * values.size()); @@ -51,13 +51,6 @@ private List> getKillEvents(List actions, String type, List return events; } - private String joinWithOr(List items) { - if(items == null || items.size() == 0) return "nothing"; - if(items.size() == 1) return items.get(0); - if(items.size() == 2) return items.get(0) + " or " + items.get(1); - else return String.join(", ", items.subList(0, items.size() - 1)) + ", or " + items.get(items.size() - 1); - } - private QuestTask makeTaskForEntityTypes(List actions, List names, List codes, int quantity) { List> events = getKillEvents(actions, "code", codes); From d5f73e8b1d6c6e41f445afdb1b162c9ef178f227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 5 Dec 2024 00:15:00 +0100 Subject: [PATCH 23/36] make task description overrides more consistent --- .../brainwine/gameserver/quest/randomquests/Collect.java | 2 +- .../java/brainwine/gameserver/quest/randomquests/Craft.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java index 89eaf892..4fa54a73 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Collect.java @@ -47,7 +47,7 @@ private QuestTask nextQuestTask(Random random, CollectItem collectItem) throws C return itemTitle; }).collect(Collectors.joining(" or ")); } else { - myTaskDescription = taskDescription; + myTaskDescription = taskDescription.replaceAll("\\{QUANTITY\\}", Integer.toString(quantity)); } return new QuestTask() diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java index cae2e7d8..ae78a6d4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Craft.java @@ -49,7 +49,11 @@ public Quest nextQuest(Random random, Player player) { } QuestTask task = new QuestTask(); - task.setDescription("Craft " + amount + " of " + joinWithOr(itemNames)); + if(taskDescription == null) { + task.setDescription("Craft " + amount + " of " + joinWithOr(itemNames)); + } else { + task.setDescription(taskDescription.replaceAll("\\{QUANTITY\\}", Integer.toString(amount))); + } task.setEvents(events); task.setQuantity(amount); From 2d410c89c1db359d21faae81c4e426cec8ea37c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 5 Dec 2024 00:15:45 +0100 Subject: [PATCH 24/36] add a more practical random quest configuration --- .../src/main/resources/random-quests.json | 165 +++++++++++++----- 1 file changed, 121 insertions(+), 44 deletions(-) diff --git a/gameserver/src/main/resources/random-quests.json b/gameserver/src/main/resources/random-quests.json index 782f6ee7..3ac1bc51 100644 --- a/gameserver/src/main/resources/random-quests.json +++ b/gameserver/src/main/resources/random-quests.json @@ -29,69 +29,146 @@ "quantity": { "random_between": [ 5, 10 ] }, "reward": { "xp": { "random_between": [ 50, 150, 10 ] }, - "crowns": 2 + "crowns": 20 } }, { "frequency": 10, "type": "kill", - "entities": { - "choices": [ - "automata/android", - "automata/cat", - "creature/armadillo" - ], - "pick": 2 - }, + "categories": [ "automata" ], "task_description": "Stop the bot uprisal! Smash 3 androids of any kind!", "quantity": 3, - "reward": { - "xp": 100, - "crowns": 2 - } + "reward": { "crowns": 20 } }, { "frequency": 10, + "type": "multistep", + "steps": [ + { "type": "collect", "tasks": [{ "items": ["building/wood"], "count": 3 }] }, + { "type": "collect", "tasks": [{ "items": ["building/brass"], "count": 6 }] }, + { "type": "collect", "tasks": [{ "items": ["building/iron"], "count": 3 }] }, + { "type": "craft", "tasks": [{ "items": ["building/door-wood"], "count": 3 }, { "items": ["building/door-brass"], "count": 3 }] } + ], + "reward": { "crowns": 20 } + }, + { + "frequency": 100, "type": "kill", - "entities": { "choices": ["creatures/crow-auto", "creatures/crow"] }, - "quantity": 1 + "categories": ["brains"], + "task_description": "Stop the brain uprising! Kill {QUANTITY} brains of any type.", + "quantity": { "random_between": [3, 5] }, + "reward": { "crowns": 20 } }, { - "frequency": 10, + "frequency": 50, + "type": "kill", + "entities": [ + "creatures/crow", + "creatures/crow-auto", + "creatures/armadillo", + "creatures/rat", + "creatures/skunk", + "creatures/roach", + "creatures/roach-large", + "creatures/bunny-ice" + ], + "task_description": "Get rid of the pests! Kill {QUANTITY} pests of any kind!", + "quantity": { "random_between": [3, 5] }, + "reward": { "crowns": 20 } + }, + { + "frequency": 70, "type": "collect", - "items": { - "choices": [ - { - "items": [ "mechanical/turret-pistol" ], - "count": { "random_between": [ 3, 5 ] } + "tasks": [ + { + "items": { + "choices": [ + "mechanical/pipe", + "mechanical/pipecopper", + "mechanical/pipeiron", + "containers/barrel", + "containers/barrel-tall", + "containers/crate-small", + "containers/crate-large", + "containers/crate-industrial-small", + "containers/crate-industrial-large", + "lighting/floorlamp", + "building/staircase", + "building/staircase-copper", + "building/staircase-iron", + "building/iron", + "building/copper", + "building/brass", + "rubble/gravestone" + ] }, - { - "items": [ "building/door-wood" ], - "count": 3 - } - ], - "pick": 1 - }, - "reward": { "xp": 100, "crowns": 2 } + "count": { "random_between": [5, 15] } + } + ], + "reward": { "crowns": 20 } }, { - "frequency": 10, - "type": "craft", - "items": { "choices": [ - { "item": "building/door-wood", "count": { "random_between": [ 3, 5 ]}}, - { "item": "building/door-brass", "count": { "random_between": [ 3, 5 ]}} - ]} + "frequency": 40, + "type": "collect", + "tasks": [ + { + "items": { + "choices": [ + "ground/crystal-blue-small", + "ground/crystal-purple-1", + "ground/crystal-red-1", + "ground/crystal-orange-small", + "ground/yellow-crystal-small", + "ground/crystal-green-small", + "ground/crystal-white-small" + ] + }, + "count": { "random_between": [1, 5] } + } + ], + "reward": { "crowns": 20 } }, { - "frequency": 10, - "type": "multistep", - "reward": { "crowns": 3 }, - "steps": [ - { "type": "collect", "items": [{ "items": ["building/wood"], "count": 3 }] }, - { "type": "collect", "items": [{ "items": ["building/brass"], "count": 6 }] }, - { "type": "collect", "items": [{ "items": ["building/iron"], "count": 3 }] }, - { "type": "craft", "items": [{ "item": "building/door-wood", "count": 3 }, { "item": "building/door-brass", "count": 3 }] } - ] + "frequency": 80, + "type": "craft", + "tasks": [ + { + "items": { + "choices": [ + "furniture/snowman", + "building/tiles-checkered", + "back/checkered", + "furniture/bed", + "furniture/bed-covered", + "furniture/bidet", + "furniture/bathtub", + "furniture/fridge", + "furniture/book-stand", + "furniture/umbrella", + "mechanical/bomb", + "mechanical/bomb-incendiary", + "mechanical/bomb-electric", + "tools/pistol", + "tools/musket", + "consumables/jerky-power", + "containers/chest", + "building/door-brass", + "mechanical/door-beefy-closed-copper", + "mechanical/door-beefy-closed-iron", + "tools/cane", + "tools/pickaxe-fine", + "building/mailbox", + "containers/wine-bottle", + "building/plug", + "containers/crate-industrial-small", + "containers/crate-industrial-large", + "building/scarecrow", + "building/tinman" + ] + } + } + ], + "reward": { "crowns": 20 } } ] } From a512675ec58515e95a9a740dbd2798f07aed760c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sun, 8 Dec 2024 14:14:41 +0100 Subject: [PATCH 25/36] fix non-entity explode bug --- gameserver/src/main/java/brainwine/gameserver/zone/Zone.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index f395592e..8d88f23f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -491,7 +491,7 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv float damage = (float)(baseDamage - distance); entity.attack(cause, item, damage, damageType); - if(entity.isDead() && cause.isPlayer()) { + if(entity.isDead() && cause != null && cause.isPlayer()) { QuestEvents.handleExplode((Player) cause, entity); } } From 68a247575b6a4a88d01e67f965787f0ad02005f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sun, 8 Dec 2024 14:56:14 +0100 Subject: [PATCH 26/36] handle quest completion as an event --- .../gameserver/quest/PlayerQuests.java | 4 +- .../gameserver/quest/QuestEvents.java | 4 ++ .../brainwine/gameserver/quest/QuestTask.java | 44 ++++++++++++++++--- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java index 31de5ed0..25038502 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java @@ -54,7 +54,7 @@ public static boolean isTaskComplete(Quest quest, Player player, int i) { boolean satisfiesQuantity = task.getQuantity() <= progress; boolean satisfiesInventory = task.getCollectInventory() == null || task.getCollectInventory().playerSatisfies(player); - return satisfiesQuantity && satisfiesInventory; + return satisfiesQuantity && satisfiesInventory && task.checkProgress(player); } public static boolean canFinishQuest(Player player, Quest quest) { @@ -115,6 +115,8 @@ public static void finishQuest(Player player, Quest quest) { progress.markAsComplete(); + QuestEvents.handleCompleteQuest(player); + sendPlayerQuestMessage(player, progress); } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java index 07cee6e6..da937f13 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java @@ -120,4 +120,8 @@ public static void handleAppearance(Player player, Map appearanc handleEvent(player, "appearance", "code", code); } } + + public static void handleCompleteQuest(Player player) { + handleEvent(player, "complete_quest"); + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java index 2fa1d8d7..53ea0508 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java @@ -22,10 +22,10 @@ public class QuestTask { private List> events = new ArrayList<>(); @JsonProperty("progress") - private List> progress = new ArrayList<>(); + private List> progressRequirements = null; @JsonProperty("qualify") - private List> qualify = new ArrayList<>(); + private List> qualify = null; @JsonProperty("action") private String action; @@ -63,6 +63,36 @@ public boolean doesQualify(Player player) { return true; } + public boolean checkProgress(Player player) { + if(this.getProgressRequirements() == null) return true; + + for(List requirement: getProgressRequirements()) { + if(requirement.isEmpty()) continue; + + if(!(requirement.get(0) instanceof String)) continue; + + switch((String) requirement.get(0)) { + case "quests_completed_in_group": + if(requirement.size() < 2) continue; + boolean found = player.getQuestProgresses().keySet().stream().anyMatch(questId -> { + QuestProgress progress = player.getQuestProgresses().get(questId); + Quest quest = Quests.get(player, questId); + if(progress == null || quest == null) return false; + + return progress.isComplete() && Objects.equals(quest.getGroup(), requirement.get(1)); + }); + + if(!found) return false; + break; + case "players_killed": + // TODO + break; + } + } + + return true; + } + public String getDescription() { return description; } @@ -90,12 +120,12 @@ public QuestTask setEvents(List> events) { return this; } - public List> getProgress() { - return progress; + public List> getProgressRequirements() { + return progressRequirements; } - public QuestTask setProgress(List> progress) { - this.progress = progress; + public QuestTask setProgressRequirements(List> progress) { + this.progressRequirements = progress; return this; } @@ -131,7 +161,7 @@ public DialogSection getDialogSection(int taskProgress) { result.setText(getDescription() + (taskProgress >= 0 ? String.format("(Progress: %d/%d)", taskProgress, getQuantity()) : "")); - if(!getQualify().isEmpty()) { + if(getQualify() != null && !getQualify().isEmpty()) { result.addItem(new DialogListItem().setText("Qualifications:")); for(List qualification : getQualify()) { result.addItem(new DialogListItem().setText(qualification.stream().map(Objects::toString).collect(Collectors.joining(" ")))); From 130fbdcb0a0d1a096c6139ebec4813cb109eb8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sun, 8 Dec 2024 16:06:35 +0100 Subject: [PATCH 27/36] refactor task complete checks --- .../gameserver/quest/PlayerQuests.java | 21 +++++-------------- .../gameserver/quest/QuestProgress.java | 2 +- .../brainwine/gameserver/quest/QuestTask.java | 8 +++++-- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java index 25038502..f3b53679 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java @@ -46,17 +46,6 @@ public static void beginQuest(Player player, Quest quest) { performAction(player, quest, QuestAction.Type.BEGIN); } - public static boolean isTaskComplete(Quest quest, Player player, int i) { - int progress = player.getQuestProgresses().get(quest.getId()).getTaskProgress(i); - - QuestTask task = quest.getTasks().get(i); - - boolean satisfiesQuantity = task.getQuantity() <= progress; - boolean satisfiesInventory = task.getCollectInventory() == null || task.getCollectInventory().playerSatisfies(player); - - return satisfiesQuantity && satisfiesInventory && task.checkProgress(player); - } - public static boolean canFinishQuest(Player player, Quest quest) { if(player == null) return false; @@ -73,7 +62,11 @@ public static boolean canFinishQuest(Player player, Quest quest) { } for(int i = 0; i < quest.getTasks().size(); i++) { - if(!isTaskComplete(quest, player, i)) { + int currentQuantity = player.getQuestProgresses().get(quest.getId()).getTaskProgress(i); + + QuestTask task = quest.getTasks().get(i); + + if(!task.checkComplete(player, currentQuantity)) { return false; } } @@ -112,13 +105,9 @@ public static void finishQuest(Player player, Quest quest) { } quest.getReward().reward(player); - progress.markAsComplete(); - QuestEvents.handleCompleteQuest(player); - sendPlayerQuestMessage(player, progress); - } public static void performAction(Player player, Quest quest, QuestAction.Type actionType) { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java index 4a536b7c..a4e27e58 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java @@ -117,7 +117,7 @@ public Map getClientStatus(Player player) { for(int i = 0; i < quest.getTasks().size(); i++) { QuestTask task = quest.getTasks().get(i); - if(task.getQuantity() <= getTaskProgress(i)) { + if(task.checkComplete(player, getTaskProgress(i))) { completedIndices.add(i); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java index 53ea0508..be69af74 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java @@ -33,7 +33,7 @@ public class QuestTask { @JsonProperty("collect_inventory") private QuestTaskCollectInventory collectInventory; - public boolean checkQualification(Player player, Object... qualification) { + private boolean checkQualification(Player player, Object... qualification) { if(player == null || qualification == null || qualification.length == 0) { return false; } @@ -63,7 +63,7 @@ public boolean doesQualify(Player player) { return true; } - public boolean checkProgress(Player player) { + private boolean checkProgressRequirements(Player player) { if(this.getProgressRequirements() == null) return true; for(List requirement: getProgressRequirements()) { @@ -93,6 +93,10 @@ public boolean checkProgress(Player player) { return true; } + public boolean checkComplete(Player player, int quantity) { + return getQuantity() <= quantity && checkProgressRequirements(player); + } + public String getDescription() { return description; } From b20570f3c1f802b1e689cd9d62810bf5f34addc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sun, 8 Dec 2024 16:39:21 +0100 Subject: [PATCH 28/36] events nullable --- .../src/main/java/brainwine/gameserver/quest/QuestTask.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java index be69af74..72a6495c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java @@ -19,7 +19,7 @@ public class QuestTask { private int quantity = 1; @JsonProperty("events") - private List> events = new ArrayList<>(); + private List> events = null; @JsonProperty("progress") private List> progressRequirements = null; @@ -94,7 +94,7 @@ private boolean checkProgressRequirements(Player player) { } public boolean checkComplete(Player player, int quantity) { - return getQuantity() <= quantity && checkProgressRequirements(player); + return (getEvents() == null || getQuantity() <= quantity) && checkProgressRequirements(player); } public String getDescription() { @@ -172,7 +172,7 @@ public DialogSection getDialogSection(int taskProgress) { } } - if(!getEvents().isEmpty()) { + if(getEvents() != null && !getEvents().isEmpty()) { result.addItem(new DialogListItem().setText("Do any of these to make progress:")); for(List event : getEvents()) { result.addItem(new DialogListItem().setText(event.stream().map(Objects::toString).collect(Collectors.joining(" ")))); From 1e1cff93d465bb9eed70675394d85c66cd3a8f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 12 Dec 2024 21:36:38 +0100 Subject: [PATCH 29/36] send xp and crown info to the client --- .../src/main/java/brainwine/gameserver/quest/Quest.java | 3 ++- .../main/java/brainwine/gameserver/quest/QuestReward.java | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java index c1fbd63a..3ff24a20 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java @@ -65,7 +65,8 @@ private void computeClientDetailsIfAbsent() { pcDetails.put("id", getId()); pcDetails.put("group", getGroup()); pcDetails.put("title", getTitle()); - pcDetails.put("xp", getReward().getXp() == null ? 0 : getReward().getXp()); + pcDetails.put("xp", getReward().getXp()); + pcDetails.put("crowns", getReward().getCrowns()); pcDetails.put("desc", getDescription()); List tasks = new ArrayList<>(); diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java index bf74934e..f23fa3d7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestReward.java @@ -31,11 +31,11 @@ public void reward(Player player) { } } - public Integer getXp() { + public int getXp() { return xp; } - public Integer getCrowns() { + public int getCrowns() { return crowns; } @@ -43,12 +43,12 @@ public List getLootCategories() { return lootCategories; } - public QuestReward setXp(Integer xp) { + public QuestReward setXp(int xp) { this.xp = xp; return this; } - public QuestReward setCrowns(Integer crowns) { + public QuestReward setCrowns(int crowns) { this.crowns = crowns; return this; } From 0555eb82ff47ee7eb259b26b3e91f5d8ed3aa0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 18 Dec 2024 15:36:56 +0100 Subject: [PATCH 30/36] add a couple more event tracking --- .../java/brainwine/gameserver/quest/QuestEvents.java | 10 +++++++++- .../gameserver/server/requests/BlockMineRequest.java | 1 + .../src/main/java/brainwine/gameserver/zone/Zone.java | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java index da937f13..50bf3f89 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestEvents.java @@ -74,7 +74,11 @@ public static void handleEventWithQuantity(Player player, int quantity, Object.. } } - public static void handleCollectItem(Player player, Item item, int quantity) { + public static void handleDig(Player player) { + handleEvent(player, "dig"); + } + + public static void handleCollectItem(Player player, Item item, int quantity) { handleEventWithQuantity(player, quantity, "collect_item", "id", item.getId()); } @@ -111,6 +115,10 @@ public static void handleExplode(Player player, Entity other) { } } + public static void handleRaid(Player player) { + handleEvent(player, "raid"); + } + public static void handleChat(Player player) { handleEvent(player, "chat"); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index f60a03c6..460d2203 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -92,6 +92,7 @@ public void process(Player player) { if(digging) { zone.digBlock(x, y); + QuestEvents.handleDig(player); return; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 8d88f23f..57aff1c2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -894,6 +894,7 @@ public void destroyGuardBlock(String dungeonId, Player destroyer) { if(guardBlocks <= 0) { dungeons.remove(dungeonId); destroyer.getStatistics().trackDungeonRaided(); + QuestEvents.handleRaid(destroyer); destroyer.notify("You raided a dungeon!", NotificationType.ACCOMPLISHMENT); destroyer.notifyPeers(String.format("%s raided a dungeon.", destroyer.getName()), NotificationType.SYSTEM); } else { From 392fdc04646dc246fe62121f539ce93e3bf34db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sat, 21 Dec 2024 00:48:43 +0100 Subject: [PATCH 31/36] fix natural block check --- .../gameserver/server/requests/BlockMineRequest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 460d2203..b02b1a1d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -183,9 +183,11 @@ public void process(Player player) { int quantity = 1; player.getStatistics().trackItemMined(item); - + + boolean trackQuest = false; if(block.isNatural()) { player.getStatistics().trackItemScavenged(item); + trackQuest = true; } zone.updateBlock(x, y, layer, 0, 0, player); @@ -209,7 +211,7 @@ public void process(Player player) { if(!inventoryItem.isAir()) { player.getInventory().addItem(inventoryItem, quantity, true); - if(block.isNatural() && layer == Layer.FRONT) { + if(trackQuest && layer == Layer.FRONT) { QuestEvents.handleCollectItem(player, inventoryItem, quantity); if(!item.equals(inventoryItem)) { QuestEvents.handleCollectItem(player, item, 1); From 960612b830ec53f88e1bdda73f41e741c5893e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 23 Dec 2024 16:41:21 +0100 Subject: [PATCH 32/36] actually check for inventory requirement --- .../brainwine/gameserver/quest/QuestTask.java | 7 +++-- .../quest/QuestTaskCollectInventory.java | 30 ++++++++++--------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java index 72a6495c..3d5e0452 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java @@ -1,6 +1,5 @@ package brainwine.gameserver.quest; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -93,8 +92,12 @@ private boolean checkProgressRequirements(Player player) { return true; } + private boolean checkCollectInventory(Player player) { + return getCollectInventory() == null || getCollectInventory().check(player); + } + public boolean checkComplete(Player player, int quantity) { - return (getEvents() == null || getQuantity() <= quantity) && checkProgressRequirements(player); + return (getEvents() == null || getQuantity() <= quantity) && checkProgressRequirements(player) && checkCollectInventory(player); } public String getDescription() { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java index d3a5aed7..60727122 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTaskCollectInventory.java @@ -4,39 +4,40 @@ import java.util.Map; import java.util.stream.Collectors; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemRegistry; import com.fasterxml.jackson.annotation.JsonCreator; import brainwine.gameserver.dialog.DialogListItem; import brainwine.gameserver.dialog.DialogSection; -import brainwine.gameserver.item.LazyItemGetter; import brainwine.gameserver.player.Player; import brainwine.gameserver.util.Pair; public class QuestTaskCollectInventory { - List> requirements; + List> requirements; @JsonCreator public QuestTaskCollectInventory(Map inp) { requirements = inp.entrySet().stream() - .map(e -> new Pair<>(new LazyItemGetter(e.getKey()), e.getValue())) + .map(e -> new Pair<>(e.getKey(), e.getValue())) .collect(Collectors.toList()); } - public List> getRequirements() { + public List> getRequirements() { return requirements; } - public QuestTaskCollectInventory setRequirements(List> requirements) { + public QuestTaskCollectInventory setRequirements(List> requirements) { this.requirements = requirements; return this; } - public boolean playerSatisfies(Player player) { + public boolean check(Player player) { if(player == null) { return false; } - for(Pair req : requirements) { - if(!player.getInventory().hasItem(req.getFirst().get(), req.getLast())) { + for(Pair req : requirements) { + if(!player.getInventory().hasItem(ItemRegistry.getItem(req.getFirst()), req.getLast())) { return false; } } @@ -50,17 +51,18 @@ public void removeFromPlayer(Player player) { return; } - for(Pair req : requirements) { - player.getInventory().removeItem(req.getFirst().get(), req.getLast()); + for(Pair req : requirements) { + player.getInventory().removeItem(ItemRegistry.getItem(req.getFirst()), req.getLast()); } } public void addDialogListItems(DialogSection section) { - for(Pair req : getRequirements()) { - section.addItem( + for(Pair req : getRequirements()) { + Item item = ItemRegistry.getItem(req.getFirst()); + if(!item.isAir()) section.addItem( new DialogListItem() - .setItem(req.getFirst().get().getCode())) - .setText(req.getFirst().get().getTitle()); + .setItem(item.getCode())) + .setText(item.getTitle()); } } From 5be65f76c6005ca472bbbeef0f3220de1484292f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 23 Dec 2024 16:49:03 +0100 Subject: [PATCH 33/36] prevent collect inventory completing from showing up --- .../main/java/brainwine/gameserver/quest/PlayerQuests.java | 2 +- .../main/java/brainwine/gameserver/quest/QuestProgress.java | 3 ++- .../src/main/java/brainwine/gameserver/quest/QuestTask.java | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java index f3b53679..06b39b6b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/PlayerQuests.java @@ -66,7 +66,7 @@ public static boolean canFinishQuest(Player player, Quest quest) { QuestTask task = quest.getTasks().get(i); - if(!task.checkComplete(player, currentQuantity)) { + if(!task.checkComplete(player, currentQuantity) || !task.checkCollectInventory(player)) { return false; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java index a4e27e58..2da7cbd1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java @@ -117,7 +117,8 @@ public Map getClientStatus(Player player) { for(int i = 0; i < quest.getTasks().size(); i++) { QuestTask task = quest.getTasks().get(i); - if(task.checkComplete(player, getTaskProgress(i))) { + // We are hiding that the collect inventory is complete even if it is. The actual check happens in canFinishQuest. + if(task.checkComplete(player, getTaskProgress(i)) && task.getCollectInventory() == null) { completedIndices.add(i); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java index 3d5e0452..2aa77c3c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java @@ -92,12 +92,12 @@ private boolean checkProgressRequirements(Player player) { return true; } - private boolean checkCollectInventory(Player player) { + public boolean checkCollectInventory(Player player) { return getCollectInventory() == null || getCollectInventory().check(player); } public boolean checkComplete(Player player, int quantity) { - return (getEvents() == null || getQuantity() <= quantity) && checkProgressRequirements(player) && checkCollectInventory(player); + return (getEvents() == null || getQuantity() <= quantity) && checkProgressRequirements(player); } public String getDescription() { From 08dad6d1329cff04bd93e6d055684abbde795811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 23 Dec 2024 17:32:19 +0100 Subject: [PATCH 34/36] improve styling of the quests dialog --- .../brainwine/gameserver/quest/QuestProgress.java | 4 ++-- .../java/brainwine/gameserver/quest/QuestTask.java | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java index 2da7cbd1..e0073544 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestProgress.java @@ -84,11 +84,11 @@ public List getDialogSection(Player player, boolean canFinishQues mainSection.setTitle(quest.getTitle()); if(PlayerQuests.canFinishQuest(player, quest)) { - mainSection.addItem(new DialogListItem().setImage("shop/premium").setText("All tasks done. Visit a quester android to claim your reward!")); + mainSection.addItem(new DialogListItem().setText("All tasks done. Visit a quester android to claim your reward!")); } for(int i = 0; i < quest.getTasks().size(); i++) { - result.add(quest.getTasks().get(i).getDialogSection(getTaskProgress(i))); + result.add(quest.getTasks().get(i).getDialogSection(player, getTaskProgress(i))); } if(canFinishQuest) { diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java index 2aa77c3c..d70ffff6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/QuestTask.java @@ -163,11 +163,16 @@ public QuestTask setCollectInventory(QuestTaskCollectInventory collectInventory) return this; } - public DialogSection getDialogSection(int taskProgress) { + public DialogSection getDialogSection(Player player, int taskProgress) { DialogSection result = new DialogSection(); - result.setText(getDescription() + (taskProgress >= 0 ? String.format("(Progress: %d/%d)", taskProgress, getQuantity()) : "")); - + String title = getDescription() + (taskProgress >= 0 ? String.format(" (Progress: %d/%d)", taskProgress, getQuantity()) : ""); + if(player == null || player.isV3()) { + result.setTitle("" + title + ""); + } else { + result.setTitle(title).setTextColor("#00ffff"); + } + if(getQualify() != null && !getQualify().isEmpty()) { result.addItem(new DialogListItem().setText("Qualifications:")); for(List qualification : getQualify()) { From 8244dd6232404f433dc94360ce112d08c2ad4c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 7 Jan 2025 19:56:23 +0300 Subject: [PATCH 35/36] quantity default value fix --- .../brainwine/gameserver/quest/randomquests/Kill.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java index 49f82cf3..88899d71 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/randomquests/Kill.java @@ -27,13 +27,13 @@ public class Kill extends RandomQuest { @RandomListItemType(String.class) private RandomList entityIds = null; @JsonProperty("quantity") - private RandomInteger quantity = null; + private RandomInteger quantity = new RandomInteger(1); @JsonProperty("category_quantity") - private RandomInteger categoryQuantity = new RandomInteger(1); + private RandomInteger categoryQuantity = null; @JsonProperty("code_quantity") - private RandomInteger codeQuantity = new RandomInteger(1); + private RandomInteger codeQuantity = null; @JsonProperty("entity_quantity") - private RandomInteger entityIdQuantity = new RandomInteger(1); + private RandomInteger entityIdQuantity = null; @JsonProperty("task_description") private String taskDescription = null; @JsonProperty("actions") From 5be8caa776b2db5738be865e0b6fd1c086aaa24e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 7 Jan 2025 21:13:28 +0300 Subject: [PATCH 36/36] show daily quest expiry time --- .../brainwine/gameserver/player/Player.java | 8 ++++++ .../gameserver/quest/DailyQuests.java | 28 +++++++++++++++++++ .../brainwine/gameserver/quest/Quest.java | 5 ++++ .../gameserver/util/ValueWithExpiry.java | 9 ++++++ 4 files changed, 50 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index f885e017..c405f1b1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -149,6 +149,7 @@ public class Player extends Entity implements CommandExecutor { private long lastHeartbeat; private long lastTrackedEntityUpdate; private long lastLandmarkVoteAt; + private long lastQuestTimeMessageAt; private Zone nextZone; private Connection connection; @@ -249,6 +250,13 @@ public void tick(float deltaTime) { } DailyQuests.tryIssueDailyQuest(this); + + long dailyQuestTimeLeft = getDailyQuest().getTimeUntilExpiry(System.currentTimeMillis()); + long dailyQuestRequiredInterval = dailyQuestTimeLeft >= 3600000 ? 600000 : 30000; + if(System.currentTimeMillis() >= lastQuestTimeMessageAt + dailyQuestRequiredInterval) { + lastQuestTimeMessageAt = System.currentTimeMillis(); + DailyQuests.sendDailyQuestTime(this, dailyQuestTimeLeft); + } } @Override diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java index 20c89b79..dc30368e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/DailyQuests.java @@ -42,4 +42,32 @@ public static void tryIssueDailyQuest(Player player) { } } } + + public static void sendDailyQuestTime(Player player, long timeLeft) { + ValueWithExpiry> currentV = player.getDailyQuest(); + + String timeString; + if(timeLeft >= 3600000) { + long val = (timeLeft / 3600000L); + timeString = val == 1 ? val + " hour" : val + " hours"; + } else { + long val = (timeLeft / 60000L); + timeString = val == 1 ? val + " minute" : val + " minutes"; + } + + if(currentV != null && currentV.getValue() != null) { + for(Quest quest : currentV.getValue()) { + QuestProgress progress = player.getQuestProgresses().get(quest.getId()); + if(progress == null) continue; + + quest.clearDetailsCache(); + String oldDescription = quest.getDescription(); + quest.setDescription("(expires in " + timeString + ") " + oldDescription); + + PlayerQuests.sendPlayerQuestMessage(player, progress); + + quest.setDescription(oldDescription); + } + } + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java index 3ff24a20..855cb152 100644 --- a/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java +++ b/gameserver/src/main/java/brainwine/gameserver/quest/Quest.java @@ -85,6 +85,11 @@ private void computeClientDetailsIfAbsent() { } } + public void clearDetailsCache() { + pcDetails = null; + mobileDetails = null; + } + @JsonIgnore public Map getPcDetails() { computeClientDetailsIfAbsent(); diff --git a/gameserver/src/main/java/brainwine/gameserver/util/ValueWithExpiry.java b/gameserver/src/main/java/brainwine/gameserver/util/ValueWithExpiry.java index 7a67f138..e8a5d6d8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/ValueWithExpiry.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/ValueWithExpiry.java @@ -68,6 +68,15 @@ public boolean isExpired(Calendar now) { return expiresAt == null || expiresAt.before(now); } + /**Return the time until expiry in milliseconds + * + * @param currentTime the time since epoch + * @return + */ + public long getTimeUntilExpiry(long currentTime) { + return expiresAt.getTimeInMillis() - currentTime; + } + /**Return the wrapped value. This doesn't check if the value is expired * * @return