From 6d7157ba95be798f8cbe3f5411d595a67383d5f7 Mon Sep 17 00:00:00 2001 From: Jordan Irwin Date: Thu, 6 Jun 2024 12:53:39 -0700 Subject: [PATCH] Change Find Rat Children quest state string to format more... ...suited for completions tracking --- .../server/entity/npc/RatKidsNPCBase.java | 39 ++-- .../server/entity/player/UpdateConverter.java | 6 +- .../server/maps/quests/FindRatChildren.java | 203 ++++++++++++++---- .../maps/quests/FindRatChildrenTest.java | 53 ++++- 4 files changed, 224 insertions(+), 77 deletions(-) diff --git a/src/games/stendhal/server/entity/npc/RatKidsNPCBase.java b/src/games/stendhal/server/entity/npc/RatKidsNPCBase.java index a4a3517de9a..dfadc8374a7 100644 --- a/src/games/stendhal/server/entity/npc/RatKidsNPCBase.java +++ b/src/games/stendhal/server/entity/npc/RatKidsNPCBase.java @@ -1,6 +1,6 @@ /* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2010 - Stendhal * + * (C) Copyright 2003-2024 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -12,12 +12,13 @@ ***************************************************************************/ package games.stendhal.server.entity.npc; -import java.util.Arrays; import java.util.List; +import java.util.Locale; import games.stendhal.common.parser.Sentence; import games.stendhal.server.entity.npc.condition.GreetingMatchesNameCondition; import games.stendhal.server.entity.player.Player; +import games.stendhal.server.maps.quests.FindRatChildren; // import org.apache.log4j.Logger; @@ -58,30 +59,20 @@ protected void createDialog() { private static class RatKidGreetingAction implements ChatAction { @Override public void fire(final Player player, final Sentence sentence, final EventRaiser npc) { - if (!player.hasQuest(QUEST_SLOT) || player.isQuestInState(QUEST_SLOT, "rejected")) { + if (!player.hasQuest(QUEST_SLOT) || player.isQuestInState(QUEST_SLOT, 0, "rejected")) { npc.say("Mother says I mustn't talk to strangers."); } else { - final String npcQuestText = player.getQuest(QUEST_SLOT); - final String[] npcDoneText = npcQuestText.split(":"); - - final String lookStr; - final String saidStr; - if (npcDoneText.length > 1) { - lookStr = npcDoneText[0].toLowerCase(); - saidStr = npcDoneText[1].toLowerCase(); - - final List list = Arrays.asList(lookStr.split(";")); - String npcName = npc.getName().toLowerCase(); - if (list.contains(npcName) || player.isQuestCompleted(QUEST_SLOT)) { - npc.say("Oh hello again."); - } else if ( npcDoneText.length > 1) { - player.setQuest(QUEST_SLOT, lookStr + ";" + npcName - + ":" + saidStr);// - npc.say("Hello my name is " + npc.getName() + ". Please tell mother that I am ok."); - player.addXP(500); - } else { - npc.say("Mother says I mustn't talk to strangers."); - } + final List found = FindRatChildren.getFoundNames(player); + String npcName = npc.getName().toLowerCase(Locale.ENGLISH); + final boolean questCompleted = player.isQuestCompleted(QUEST_SLOT); + if (questCompleted || found.contains(npcName)) { + npc.say("Oh hello again."); + } else if (!questCompleted) { + FindRatChildren.addFoundName(player, npcName); + npc.say("Hello my name is " + npc.getName() + ". Please tell mother that I am ok."); + player.addXP(500); + } else { + npc.say("Mother says I mustn't talk to strangers."); } } } diff --git a/src/games/stendhal/server/entity/player/UpdateConverter.java b/src/games/stendhal/server/entity/player/UpdateConverter.java index 3a3e15d735d..207f0bb785b 100644 --- a/src/games/stendhal/server/entity/player/UpdateConverter.java +++ b/src/games/stendhal/server/entity/player/UpdateConverter.java @@ -31,6 +31,7 @@ import games.stendhal.server.entity.slot.EntitySlot; import games.stendhal.server.entity.slot.KeyedSlot; import games.stendhal.server.entity.slot.PlayerSlot; +import games.stendhal.server.maps.quests.FindRatChildren; import games.stendhal.server.util.TimeUtil; import marauroa.common.Pair; import marauroa.common.game.RPObject; @@ -674,7 +675,7 @@ public static void updateQuests(final Player player) { - (TimeUtil.MINUTES_IN_HALF_YEAR / 2 * TimeUtil.MILLISECONDS_IN_MINUTE))); } - // 1.47: support completions tracking + // 1.48: support completions tracking in Chocolate for Elisabeth & Ice Cream for Annie for (String slot: Arrays.asList("chocolate_for_elisabeth", "icecream_for_annie")) { if (!player.hasQuest(slot)) { continue; @@ -683,6 +684,9 @@ public static void updateQuests(final Player player) { player.setQuest(slot, 2, "1"); } } + + // update Find Rat Children + FindRatChildren.checkPlayerUpdate(player); } diff --git a/src/games/stendhal/server/maps/quests/FindRatChildren.java b/src/games/stendhal/server/maps/quests/FindRatChildren.java index eafddd8adda..da80e0084e6 100644 --- a/src/games/stendhal/server/maps/quests/FindRatChildren.java +++ b/src/games/stendhal/server/maps/quests/FindRatChildren.java @@ -16,8 +16,7 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; - -import org.apache.log4j.Logger; +import java.util.Locale; import games.stendhal.common.grammar.Grammar; import games.stendhal.common.parser.Sentence; @@ -26,6 +25,7 @@ import games.stendhal.server.entity.npc.ConversationStates; import games.stendhal.server.entity.npc.EventRaiser; import games.stendhal.server.entity.npc.SpeakerNPC; +import games.stendhal.server.entity.npc.action.MultipleActions; import games.stendhal.server.entity.npc.action.SetQuestAction; import games.stendhal.server.entity.npc.action.SetQuestAndModifyKarmaAction; import games.stendhal.server.entity.npc.condition.AndCondition; @@ -71,8 +71,6 @@ */ public class FindRatChildren extends AbstractQuest { - private static Logger logger = Logger.getLogger(FindRatChildren.class); - private static final String QUEST_SLOT = "find_rat_kids"; // twenty four hours @@ -88,36 +86,172 @@ public String getSlotName() { return QUEST_SLOT; } - private List missingNames(final Player player) { + /** + * Retrieves names of children player still needs to find and report. + * + * @param player + * Player doing quest. + * @return + * Missing children's names. + */ + private static List missingNames(final Player player) { if (!player.hasQuest(QUEST_SLOT)) { return NEEDED_KIDS; } /* * the format of the list quest slot is - * "looking;name;name;...:said;name;name;..." + * "found=name,name,...;said=name,name,..." */ // put the children name to lower case so we can match it, however the player wrote the name - final String npcDoneText = player.getQuest(QUEST_SLOT).toLowerCase(); - final String[] doneAndFound = npcDoneText.split(":"); + final String[] tmp = player.getQuest(QUEST_SLOT, 1).toLowerCase(Locale.ENGLISH).split("="); + if (tmp.length < 2) { + return NEEDED_KIDS; + } + final List said = Arrays.asList(tmp[1].split(",")); final List result = new LinkedList(); - if (doneAndFound.length > 1) { - final String[] done = doneAndFound[1].split(";"); - final List doneList = Arrays.asList(done); - for (final String name : NEEDED_KIDS) { - if (!doneList.contains(name)) { - result.add(name); - } + for (final String name: NEEDED_KIDS) { + if (!said.contains(name)) { + result.add(name); } } return result; } + /** + * Checks if quest state is active. + * + * @param player + * Player doing quest. + * @return + * {@code true} if player has quest and is in active state. + */ + private static boolean lookingForKids(final Player player) { + return new QuestActiveCondition(QUEST_SLOT).fire(player, null, null); + } + + /** + * Adds a name to player's quest state when NPC is found. + * + * @param player + * Player doing quest. + * @param name + * Name of found child. + */ + public static void addFoundName(final Player player, final String name) { + if (!lookingForKids(player)) { + return; + } + String found = player.getQuest(QUEST_SLOT, 0); + if (found.length() > "found=".length()) { + found += ","; + } + found += name; + player.setQuest(QUEST_SLOT, 0, found); + } + + /** + * Adds a name to player's quest state when reported to Agnus. + * + * @param player + * Player doing quest. + * @param name + * Name of found child. + */ + private static void addSaidName(final Player player, final String name) { + if (!lookingForKids(player)) { + return; + } + String said = player.getQuest(QUEST_SLOT, 1); + if (said.length() > "said=".length()) { + said += ","; + } + said += name; + player.setQuest(QUEST_SLOT, 1, said); + } + + /** + * Retrieves names of rat children player has talked to. + * + * @param player + * Player doing quest. + * @return + * List of names of children player has talked to. + */ + public static List getFoundNames(final Player player) { + final List names = new ArrayList<>(); + if (!lookingForKids(player)) { + return names; + } + final String[] tmp = player.getQuest(QUEST_SLOT, 0).toLowerCase(Locale.ENGLISH).split("="); + if (tmp.length < 2) { + return names; + } + names.addAll(Arrays.asList(tmp[1].split(","))); + return names; + } + + /** + * Retrieves names of rat children player has reported to Agnus. + * + * @param player + * Player doing quest. + * @return + * List of names of children player has reported. + */ + public static List getSaidNames(final Player player) { + final List names = new ArrayList<>(); + if (!lookingForKids(player)) { + return names; + } + final String[] tmp = player.getQuest(QUEST_SLOT, 1).toLowerCase(Locale.ENGLISH).split("="); + if (tmp.length < 2) { + return names; + } + names.addAll(Arrays.asList(tmp[1].split(","))); + return names; + } + + /** + * Updates formatting of player's quest state string. + * + * @param player + * Player being updated. + */ + public static void checkPlayerUpdate(final Player player) { + final String state = player.getQuest(QUEST_SLOT); + // 1.48: support completions tracking in Find Rat Children + if (!player.hasQuest(QUEST_SLOT) || !state.startsWith("looking")) { + return; + } + final List found = new ArrayList<>(); + final List said = new ArrayList<>(); + final String[] tmp = state.split(":"); + if (tmp.length > 0) { + for (final String name: tmp[0].split(";")) { + if ("looking".equals(name)) { + continue; + } + found.add(name); + } + } + if (tmp.length > 1) { + for (final String name: tmp[1].split(";")) { + if ("said".equals(name)) { + continue; + } + said.add(name); + } + } + player.setQuest(QUEST_SLOT, "found=" + String.join(",", found) + ";said=" + + String.join(",", said)); + } + private void askingStep() { final SpeakerNPC npc = npcs.get("Agnus"); npc.add(ConversationStates.ATTENDING, ConversationPhrases.QUEST_MESSAGES, - new OrCondition(new QuestNotStartedCondition(QUEST_SLOT), new QuestInStateCondition(QUEST_SLOT, "rejected")), + new OrCondition(new QuestNotStartedCondition(QUEST_SLOT), new QuestInStateCondition(QUEST_SLOT, 0, "rejected")), ConversationStates.QUEST_OFFERED, "I feel so worried. If I only knew my #children were safe I would feel better.", null); @@ -151,7 +285,9 @@ private void askingStep() { null, ConversationStates.ATTENDING, "That's so nice of you. Good luck searching for them.", - new SetQuestAction(QUEST_SLOT, "looking:said")); + new MultipleActions( + new SetQuestAction(QUEST_SLOT, 0, "found="), + new SetQuestAction(QUEST_SLOT, 1, "said="))); npc.add( ConversationStates.QUEST_OFFERED, @@ -159,7 +295,7 @@ private void askingStep() { null, ConversationStates.ATTENDING, "Oh. Never mind. I'm sure someone else would be glad to help me.", - new SetQuestAndModifyKarmaAction(QUEST_SLOT, "rejected", -15.0)); + new SetQuestAndModifyKarmaAction(QUEST_SLOT, 0, "rejected", -15.0)); npc.add( ConversationStates.QUEST_OFFERED, @@ -197,35 +333,17 @@ private void retrievingStep() { new ChatAction() { @Override public void fire(final Player player, final Sentence sentence, final EventRaiser npc) { - final String npcQuestText = player.getQuest(QUEST_SLOT).toLowerCase(); - final String[] npcDoneText = npcQuestText.split(":"); - final String lookingStr; - final String saidStr; - if (npcDoneText.length > 1) { - lookingStr = npcDoneText[0]; - saidStr = npcDoneText[1]; - } else { - // compatibility with broken quests - should never happen - logger.warn("Player " + player.getTitle() + " found with find_rat_kids quest slot in state " + player.getQuest(QUEST_SLOT) + " - now setting this to done."); - player.setQuest(QUEST_SLOT, "done"); - npc.say("Sorry, it looks like you have already found them after all. I got confused."); - player.notifyWorldAboutChanges(); - npc.setCurrentState(ConversationStates.ATTENDING); - return; - } - - final List looking = Arrays.asList(lookingStr.split(";")); - final List said = Arrays.asList(saidStr.split(";")); + final List found = getFoundNames(player); + final List said = getSaidNames(player); String reply = ""; List missing = missingNames(player); final boolean isMissing = missing.contains(name); - if (isMissing && looking.contains(name) && !said.contains(name)) { + if (isMissing && found.contains(name) && !said.contains(name)) { // we haven't said the name yet so we add it to the list - player.setQuest(QUEST_SLOT, lookingStr - + ":" + saidStr + ";" + name); + addSaidName(player, name); reply = "Thank you."; - } else if (!looking.contains(name)) { + } else if (!found.contains(name)) { // we have said it was a valid name but haven't seen them reply = "I don't think you actually checked if they were ok."; } else if (!isMissing && said.contains(name)) { @@ -246,7 +364,8 @@ public void fire(final Player player, final Sentence sentence, final EventRaiser player.addKarma(15); reply += " Now that I know my kids are safe, I can set my mind at rest."; npc.say(reply); - player.setQuest(QUEST_SLOT, "done;" + System.currentTimeMillis()); + player.setQuest(QUEST_SLOT, 0, "done"); + player.setQuest(QUEST_SLOT, 1, String.valueOf(System.currentTimeMillis())); player.notifyWorldAboutChanges(); npc.setCurrentState(ConversationStates.ATTENDING); } diff --git a/tests/games/stendhal/server/maps/quests/FindRatChildrenTest.java b/tests/games/stendhal/server/maps/quests/FindRatChildrenTest.java index 0cc555c3f40..d26dcba19f6 100644 --- a/tests/games/stendhal/server/maps/quests/FindRatChildrenTest.java +++ b/tests/games/stendhal/server/maps/quests/FindRatChildrenTest.java @@ -1,6 +1,6 @@ /* $Id$ */ /*************************************************************************** - * (C) Copyright 2003-2010 - Stendhal * + * (C) Copyright 2003-2024 - Stendhal * *************************************************************************** *************************************************************************** * * @@ -13,6 +13,7 @@ package games.stendhal.server.maps.quests; import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; @@ -129,7 +130,7 @@ public void testStartQuest() { assertEquals("Bye", getReply(npc)); // check quest slot - assertEquals(player.getQuest(QUEST_SLOT),"looking:said"); + assertThat(player.getQuest(QUEST_SLOT), is("found=;said=")); } @Test @@ -165,6 +166,8 @@ public void testMeetingCodyAfterQuestStarted() { // the state wasn't remembered across the new test method so we need to set it to what it was when we ended the last player.setQuest(QUEST_SLOT, "looking:said"); + FindRatChildren.checkPlayerUpdate(player); + assertThat(player.getQuest(QUEST_SLOT), is("found=;said=")); // remember the xp and karma, did it go up? final int xp = player.getXP(); @@ -173,16 +176,16 @@ public void testMeetingCodyAfterQuestStarted() { assertEquals("Hello my name is Cody. Please tell mother that I am ok.", getReply(npc)); // [11:49] kymara earns 500 experience points. - assertThat(player.getXP(), greaterThan(xp)); // check quest slot - assertEquals(player.getQuest(QUEST_SLOT),"looking;cody:said"); + assertThat(player.getQuest(QUEST_SLOT), is("found=cody;said=")); + assertThat(player.getXP(), greaterThan(xp)); // return after having met in this quest run en.step(player, "hi"); assertEquals("Oh hello again.", getReply(npc)); // check quest slot - assertEquals(player.getQuest(QUEST_SLOT),"looking;cody:said"); + assertThat(player.getQuest(QUEST_SLOT), is("found=cody;said=")); } @Test @@ -193,6 +196,8 @@ public void testNamingKidsMet() { // the state wasn't remembered across the new test method so we need to set it to what it was when we ended the last player.setQuest(QUEST_SLOT, "looking;cody:said"); + FindRatChildren.checkPlayerUpdate(player); + assertThat(player.getQuest(QUEST_SLOT), is("found=cody;said=")); en.step(player, "hi"); assertEquals("If you found any of my #children, please tell me their name.", getReply(npc)); @@ -206,7 +211,7 @@ public void testNamingKidsMet() { assertEquals("No problem, come back later.", getReply(npc)); // check quest slot - assertEquals(player.getQuest(QUEST_SLOT),"looking;cody:said;cody"); + assertThat(player.getQuest(QUEST_SLOT), is("found=cody;said=cody")); } @Test @@ -216,6 +221,8 @@ public void testMeetingRemainingKids() { // the state wasn't remembered across the new test method so we need to set it to what it was when we ended the last player.setQuest(QUEST_SLOT, "looking;cody:said;cody"); + FindRatChildren.checkPlayerUpdate(player); + assertThat(player.getQuest(QUEST_SLOT), is("found=cody;said=cody")); en.step(player, "hi"); assertEquals("Hello my name is Mariel. Please tell mother that I am ok.", getReply(npc)); @@ -239,17 +246,19 @@ public void testMeetingRemainingKids() { // [11:50] kymara earns 500 experience points. // check quest slot - assertEquals(player.getQuest(QUEST_SLOT),"looking;cody;mariel;opal;avalon:said;cody"); + assertThat(player.getQuest(QUEST_SLOT), is("found=cody,mariel,opal,avalon;said=cody")); } + @Test public void testNamingRemainingKids() { - npc = SingletonRepository.getNPCList().get("Agnus"); en = npc.getEngine(); // the state wasn't remembered across the new test method so we need to set it to what it was when we ended the last player.setQuest(QUEST_SLOT, "looking;cody;mariel;opal;avalon:said;cody"); + FindRatChildren.checkPlayerUpdate(player); + assertThat(player.getQuest(QUEST_SLOT), is("found=cody,mariel,opal,avalon;said=cody")); en.step(player, "hi"); assertEquals("If you found any of my #children, please tell me their name.", getReply(npc)); @@ -259,7 +268,7 @@ public void testNamingRemainingKids() { assertEquals("No problem, come back later.", getReply(npc)); // check quest slot - assertEquals(player.getQuest(QUEST_SLOT),"looking;cody;mariel;opal;avalon:said;cody;mariel"); + assertThat(player.getQuest(QUEST_SLOT), is("found=cody,mariel,opal,avalon;said=cody,mariel")); // remember the xp and karma, did it go up? final int xp = player.getXP(); @@ -303,7 +312,7 @@ public void testReturningBeforeTimePassed() { assertEquals("Bye", getReply(npc)); } - @Test + @Test public void testReturningAfterTimePassed() { // [11:51] Admin kymara changed your state of the quest 'find_rat_kids' from 'done;1270205441630' to 'done;1' @@ -332,4 +341,28 @@ public void testReturningAfterTimePassed() { // [11:51] kymara earns 500 experience points. en.step(player, "bye"); } + + @Test + public void testUpdates() { + // 1.47-1.48 + String found = "looking;"; + String said = "said;"; + String foundResult = "found="; + String saidResult = "said="; + for (final String name: new String[] {"avalon", "cody", "mariel", "opal"}) { + if (!"avalon".equals(name)) { + found += ";"; + said += ";"; + foundResult += ","; + saidResult += ","; + } + found += name; + said += name; + foundResult += name; + saidResult += name; + player.setQuest(QUEST_SLOT, found + ":" + said); + FindRatChildren.checkPlayerUpdate(player); + assertThat(player.getQuest(QUEST_SLOT), is(foundResult + ";" + saidResult)); + } + } }