Skip to content

Commit

Permalink
Allow requesting different item for Ultimate Collector quest...
Browse files Browse the repository at this point in the history
...after 6 months

Closes: arianne#719
  • Loading branch information
AntumDeluge committed May 14, 2024
1 parent 04e0516 commit 3c30c36
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 32 deletions.
1 change: 1 addition & 0 deletions doc/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Changelog
- renamed chef Patrick to Preston
- new achievements:
- 30 Minutes or Less: Deliver 25 hot pizzas
- Balduin allows requesting different item for Ultimate Collector quest

*web client*
- fixed text extending past edge of notification bubbles
Expand Down
11 changes: 11 additions & 0 deletions src/games/stendhal/server/entity/player/UpdateConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.util.TimeUtil;
import marauroa.common.Pair;
import marauroa.common.game.RPObject;
import marauroa.common.game.RPSlot;
Expand Down Expand Up @@ -662,6 +663,16 @@ public static void updateQuests(final Player player) {
if (player.hasQuest(questSlot) && !slotState.startsWith(";")) {
player.setQuest(questSlot, ";" + slotState);
}

// 1.47: Ultimate Collector supports aborting & requesting another item after 6 months
questSlot = "ultimate_collector";
if (player.hasQuest(questSlot) && !"done".equals(player.getQuest(questSlot, 0))
&& "".equals(player.getQuest(questSlot, 1))) {
// since we don't know when quest was started assume that at least half the required time (3
// months) have passed
player.setQuest(questSlot, 1, Long.toString(System.currentTimeMillis()
- (TimeUtil.MINUTES_IN_HALF_YEAR / 2 * TimeUtil.MILLISECONDS_IN_MINUTE)));
}
}


Expand Down
124 changes: 104 additions & 20 deletions src/games/stendhal/server/maps/quests/UltimateCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import games.stendhal.server.entity.npc.action.MultipleActions;
import games.stendhal.server.entity.npc.action.SayRequiredItemAction;
import games.stendhal.server.entity.npc.action.SetQuestAction;
import games.stendhal.server.entity.npc.action.SetQuestToTimeStampAction;
import games.stendhal.server.entity.npc.action.StartRecordingRandomItemCollectionAction;
import games.stendhal.server.entity.npc.condition.AndCondition;
import games.stendhal.server.entity.npc.condition.GreetingMatchesNameCondition;
Expand All @@ -47,9 +48,11 @@
import games.stendhal.server.entity.npc.condition.QuestCompletedCondition;
import games.stendhal.server.entity.npc.condition.QuestNotCompletedCondition;
import games.stendhal.server.entity.npc.condition.QuestNotStartedCondition;
import games.stendhal.server.entity.npc.condition.TimePassedCondition;
import games.stendhal.server.entity.player.Player;
import games.stendhal.server.events.SoundEvent;
import games.stendhal.server.maps.Region;
import games.stendhal.server.util.TimeUtil;


/**
Expand Down Expand Up @@ -214,11 +217,16 @@ private void checkCollectingQuests() {

}

private void requestItem() {

final SpeakerNPC npc = npcs.get("Balduin");
final Map<String,Integer> items = new HashMap<String, Integer>();

/**
* Determines items to select from.
*
* @param exclude
* An item name to exclude from returned value or {@code null} to include all. Used to prevent
* re-requesting same item when player asks for "another".
* @return
* Items that may be requested from player.
*/
private Map<String, Integer> getItems(final String exclude) {
/* Updated 2022-05-22
*
* Rarity calculations (lower means more rare):
Expand All @@ -231,20 +239,28 @@ private void requestItem() {
* Items given as rewards from quests or otherwise acquirable via
* methods other than creature drops should not be included.
*/
final Map<String, Integer> items = new HashMap<>();
items.put("nihonto", 1); // 1.39
items.put("magic twoside axe", 1); // 1.72
items.put("imperator sword", 1); // 0.33
items.put("durin axe", 1); // 0.39
items.put("vulcano hammer", 1); // 0.18
items.put("xeno sword", 1); // 0.67
items.put("black scythe", 1); // 0.09
items.put("chaos dagger", 1); // 2.0
items.put("black sword", 1); // 0.15
items.put("golden orc sword", 1); // 0.09
items.put("ice war hammer", 1); // 0.15
items.put("orcish sword", 1); // 0.86
items.put("black halberd", 1); // 0.12
if (exclude != null) {
items.remove(exclude);
}
return items;
}

items.put("nihonto",1); // 1.39
items.put("magic twoside axe",1); // 1.72
items.put("imperator sword",1); // 0.33
items.put("durin axe",1); // 0.39
items.put("vulcano hammer",1); // 0.18
items.put("xeno sword",1); // 0.67
items.put("black scythe",1); // 0.09
items.put("chaos dagger",1); // 2.0
items.put("black sword",1); // 0.15
items.put("golden orc sword",1); // 0.09
items.put("ice war hammer",1); // 0.15
items.put("orcish sword",1); // 0.86
items.put("black halberd",1); // 0.12
private void requestItem() {
final SpeakerNPC npc = npcs.get("Balduin");

// If all quests are completed, ask for an item
npc.add(ConversationStates.ATTENDING,
Expand All @@ -264,8 +280,11 @@ private void requestItem() {
new QuestCompletedCondition(IMMORTAL_SWORD_QUEST_SLOT)),
ConversationStates.ATTENDING,
null,
new StartRecordingRandomItemCollectionAction(QUEST_SLOT, items, "Well, you've certainly proved to the residents of Faiumoni " +
"that you could be the ultimate collector, but I have one more task for you. Please bring me [item]."));
new MultipleActions(
new StartRecordingRandomItemCollectionAction(QUEST_SLOT, 0, getItems(null),
"Well, you've certainly proved to the residents of Faiumoni that you could be the"
+ " ultimate collector, but I have one more task for you. Please bring me [item]."),
new SetQuestToTimeStampAction(QUEST_SLOT, 1)));
}

private void collectItem() {
Expand Down Expand Up @@ -329,6 +348,58 @@ private void offerSteps() {
"I'll buy black items from you when you have completed each #challenge I set you.", null);
}

private void abortQuest() {
final SpeakerNPC npc = npcs.get("Balduin");
// approximately 6 months
final int expireTime = TimeUtil.MINUTES_IN_HALF_YEAR;

npc.add(
ConversationStates.ATTENDING,
ConversationPhrases.ABORT_MESSAGES,
new AndCondition(
new QuestActiveCondition(QUEST_SLOT),
new NotCondition(new TimePassedCondition(QUEST_SLOT, 1, expireTime))
),
ConversationStates.ATTENDING,
null,
new SayRequiredItemAction(QUEST_SLOT, 0, "You are on a quest to find [item]. You cannot"
+ " request a new item yet."));

npc.add(
ConversationStates.ATTENDING,
ConversationPhrases.ABORT_MESSAGES,
new AndCondition(
new QuestActiveCondition(QUEST_SLOT),
new TimePassedCondition(QUEST_SLOT, 1, expireTime)
),
ConversationStates.QUEST_OFFERED,
null,
new SayRequiredItemAction(QUEST_SLOT, 0, "You are on a quest to find [item]. Would you like"
+ " to look for a different item?"));

npc.add(
ConversationStates.QUEST_OFFERED,
ConversationPhrases.YES_MESSAGES,
new AndCondition(
new QuestActiveCondition(QUEST_SLOT),
new TimePassedCondition(QUEST_SLOT, 1, expireTime)
),
ConversationStates.ATTENDING,
null,
new RequestAnotherAction());

npc.add(
ConversationStates.QUEST_OFFERED,
ConversationPhrases.NO_MESSAGES,
new AndCondition(
new QuestActiveCondition(QUEST_SLOT),
new TimePassedCondition(QUEST_SLOT, 1, expireTime)
),
ConversationStates.ATTENDING,
null,
new SayRequiredItemAction(QUEST_SLOT, 0, "Then bring me [item]."));
}

private void replaceLRSwords() {
final SpeakerNPC npc = npcs.get("Balduin");
final Map<String, Integer> prices = SingletonRepository.getShopsList().get("twohandswords");
Expand Down Expand Up @@ -468,6 +539,7 @@ public void addToWorld() {
requestItem();
collectItem();
offerSteps();
abortQuest();
replaceLRSwords();
}

Expand All @@ -491,4 +563,16 @@ public String getNPCName() {
public String getRegion() {
return Region.ADOS_SURROUNDS;
}


private class RequestAnotherAction implements ChatAction {
@Override
public void fire(Player player, Sentence sentence, EventRaiser npc) {
final String previousItem = player.getQuest(QUEST_SLOT, 0).split("=")[0];
new StartRecordingRandomItemCollectionAction(QUEST_SLOT, 0, getItems(previousItem),
"Perhaps finding " + Grammar.a_noun(previousItem) + " proved to be too difficult for you."
+ " This time, I want you to find [item].").fire(player, sentence, npc);
new SetQuestToTimeStampAction(QUEST_SLOT, 1).fire(player, sentence, npc);
}
}
}
118 changes: 106 additions & 12 deletions tests/games/stendhal/server/maps/quests/UltimateCollectorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@

import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.oneOf;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static utilities.SpeakerNPCTestHelper.getReply;

import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import games.stendhal.common.grammar.Grammar;
import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPZone;
import games.stendhal.server.entity.item.Item;
Expand All @@ -35,11 +39,14 @@
import games.stendhal.server.entity.npc.fsm.Engine;
import games.stendhal.server.entity.player.Player;
import games.stendhal.server.maps.ados.rock.WeaponsCollectorNPC;
import games.stendhal.server.util.TimeUtil;
import utilities.PlayerTestHelper;
import utilities.QuestHelper;

public class UltimateCollectorTest {

private static final Logger logger = Logger.getLogger(UltimateCollectorTest.class);

private Player player = null;
private SpeakerNPC npc = null;
private Engine en = null;
Expand All @@ -66,25 +73,36 @@ public void tearDown() throws Exception {
SingletonRepository.getNPCList().remove("Balduin");
}

@Test
public void testQuest() {



npc = SingletonRepository.getNPCList().get("Balduin");
en = npc.getEngine();
// -----------------------------------------------

private void initPrerequisites(boolean all) {
// [22:23] Admin kymara changed your state of the quest 'weapons_collector' from '' to 'done'
// [22:23] Changed the state of quest 'weapons_collector' from '' to 'done'

player.setQuest("weapons_collector", "done");

// [22:23] Admin kymara changed your state of the quest 'weapons_collector2' from 'null' to 'done'
// [22:23] Changed the state of quest 'weapons_collector2' from 'null' to 'done'

player.setQuest("weapons_collector2", "done");

if (all) {
player.setQuest("mithril_cloak", "done");
player.setQuest("mithrilshield_quest", "done");
player.setQuest("immortalsword_quest", "done");
player.setQuest("club_thorns", "done");
player.setQuest("soldier_henry", "done");
player.setQuest("cloaks_collector_2", "done");
player.setQuest("cloaks_for_bario", "done");
player.setQuest("elvish_armor", "done");
player.setQuest("obsidian_knife", "done");
player.setQuest("vs_quest", "done");
}
}

@Test
public void testQuest() {
npc = SingletonRepository.getNPCList().get("Balduin");
en = npc.getEngine();
// -----------------------------------------------

initPrerequisites(false);

en.step(player, "hi");
assertEquals("Greetings old friend. I have another collecting #challenge for you.", getReply(npc));
en.step(player, "challenge");
Expand Down Expand Up @@ -363,4 +381,80 @@ private void testReplaceSwords() {
npc.getName(),
false));
}

@Test
public void testAbort() {
npc = SingletonRepository.getNPCList().get("Balduin");
en = npc.getEngine();
final String questSlot = "ultimate_collector";

initPrerequisites(true);

assertThat(player.hasQuest(questSlot), is(false));

assertThat(en.step(player, "hi"), is(true));
assertThat(getReply(npc),
is("Greetings old friend. I have another collecting #challenge for you."));
assertThat(en.step(player, "challenge"), is(true));
assertThat(getReply(npc),
startsWith("Well, you've certainly proved to the residents of Faiumoni that you could be"
+ " the ultimate collector, but I have one more task for you. Please bring me "));

assertThat(player.hasQuest(questSlot), is(true));
final String initialItem = player.getQuest(questSlot, 0).split("=")[0];
logger.info("initial item: " + initialItem);

final long startTime = Long.parseLong(player.getQuest(questSlot, 1));
final long msInHalfYear = TimeUtil.MINUTES_IN_HALF_YEAR * TimeUtil.MILLISECONDS_IN_MINUTE;

// no time has passed
assertThat(en.step(player, "another"), is(true));
assertThat(getReply(npc), is("You are on a quest to find " + Grammar.a_noun(initialItem)
+ ". You cannot request a new item yet."));

// approx. 3 months have passed (2,190 hours)
player.setQuest(questSlot, 1, String.valueOf(startTime - (msInHalfYear / 2)));
assertThat(en.step(player, "another"), is(true));
assertThat(getReply(npc), is("You are on a quest to find " + Grammar.a_noun(initialItem)
+ ". You cannot request a new item yet."));

// 26 weeks have passed (4,368 hours, approx half year)
player.setQuest(questSlot, 1, String.valueOf(startTime - (TimeUtil.MILLISECONDS_IN_WEEK * 26)));
assertThat(en.step(player, "another"), is(true));
assertThat(getReply(npc), is("You are on a quest to find " + Grammar.a_noun(initialItem)
+ ". You cannot request a new item yet."));

// half year has passed (4,380 hours)
player.setQuest(questSlot, 1, String.valueOf(startTime - msInHalfYear));
assertThat(en.step(player, "another"), is(true));
assertThat(getReply(npc), is("You are on a quest to find " + Grammar.a_noun(initialItem)
+ ". Would you like to look for a different item?"));
// doesn't want a new item
assertThat(en.step(player, "no"), is(true));
assertThat(getReply(npc), is("Then bring me " + Grammar.a_noun(initialItem) + "."));

// requested item should not have changed
assertThat(player.getQuest(questSlot, 0).split("=")[0], is(initialItem));

// ask again
assertThat(en.step(player, "another"), is(true));
assertThat(getReply(npc), is("You are on a quest to find " + Grammar.a_noun(initialItem)
+ ". Would you like to look for a different item?"));
// does want a new item
assertThat(en.step(player, "yes"), is(true));

final String newItem = player.getQuest(questSlot, 0).split("=")[0];

assertThat(getReply(npc), is("Perhaps finding " + Grammar.a_noun(initialItem)
+ " proved to be too difficult for you. This time, I want you to find "
+ Grammar.a_noun(newItem) + "."));

logger.info("new item: " + newItem + " != " + initialItem);
assertThat(newItem, not(initialItem));

// ask for another immediately after receiving new quest
assertThat(en.step(player, "another"), is(true));
assertThat(getReply(npc), is("You are on a quest to find " + Grammar.a_noun(newItem)
+ ". You cannot request a new item yet."));
}
}

0 comments on commit 3c30c36

Please sign in to comment.