<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">/***************************************************************************
 *                   (C) Copyright 2003-2022 - Arianne                     *
 ***************************************************************************
 ***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
package scripts.quest;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static utilities.SpeakerNPCTestHelper.getReply;

import java.util.Arrays;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import games.stendhal.server.core.engine.SingletonRepository;
import games.stendhal.server.core.engine.StendhalRPZone;
//import games.stendhal.server.core.events.TurnListener;
import games.stendhal.server.core.rule.EntityManager;
import games.stendhal.server.entity.Entity;
import games.stendhal.server.entity.item.Item;
import games.stendhal.server.entity.item.MetalDetector;
import games.stendhal.server.entity.npc.ConversationStates;
import games.stendhal.server.entity.npc.NPCList;
import games.stendhal.server.entity.npc.SpeakerNPC;
import games.stendhal.server.entity.npc.fsm.Engine;
import utilities.LuaTestHelper;


public class LostEngagementRingTest extends LuaTestHelper {

	private static final EntityManager entityMan = SingletonRepository.getEntityManager();

	private SpeakerNPC ari;
	private SpeakerNPC emma;
	private SpeakerNPC sawyer;
	private StendhalRPZone fado;
	private StendhalRPZone athor;
	private StendhalRPZone pawnShop;

	private final String slot = "lost_engagement_ring";


	@Before
	public void setUp() {
		setUpZone("0_fado_city");

		world.addRPZone("dummy", new StendhalRPZone("0_athor_island"));
		world.addRPZone("dummy", new StendhalRPZone("int_deniran_pawn_shop"));

		load("data/script/region/fado/city/EngagedCouple.lua");
		load("data/script/quest/LostEngagementRing.lua");
		load("data/script/region/deniran/city/interior/pawn_shop/BrokerNPC.lua");
		load("data/script/region/deniran/city/interior/pawn_shop/ItemsOnShelf.lua");
	}

	@After
	@Override
	public void tearDown() {
		super.tearDown();

		pawnShop.remove(sawyer);

		// fado is handled in super method
		world.removeZone(athor);
		world.removeZone(pawnShop);
	}

	@Test
	public void init() {
		initZones();
		initEntities();

		testWithoutQuestLoaded();
		testWithQuestLoaded();
	}

	private void initZones() {
		fado = world.getZone("0_fado_city");
		athor = world.getZone("0_athor_island");
		pawnShop = world.getZone("int_deniran_pawn_shop");

		assertNotNull(fado);
		assertNotNull(athor);
		assertNotNull(pawnShop);
	}

	private void initEntities() {
		final NPCList npcs = SingletonRepository.getNPCList();
		ari = npcs.get("Ari");
		emma = npcs.get("Emma");
		sawyer = npcs.get("Sawyer");

		assertNotNull(ari);
		assertEquals("0_fado_city", ari.getZone().getName());
		assertEquals(74, ari.getX());
		assertEquals(56, ari.getY());

		assertNotNull(emma);
		assertEquals("0_fado_city", emma.getZone().getName());
		assertEquals(75, emma.getX());
		assertEquals(56, emma.getY());

		assertNotNull(sawyer);
		assertEquals("int_deniran_pawn_shop", sawyer.getZone().getName());
		assertEquals(18, sawyer.getX());
		assertEquals(5, sawyer.getY());

		addPlayerToWorld();
		assertNotNull(player);
		assertEquals("0_fado_city", player.getZone().getName());
		assertFalse(player.hasQuest(slot));
	}

	private void leaveCurrentZone() {
		final StendhalRPZone currentZone = player.getZone();
		if (currentZone != null) {
			currentZone.remove(player);
		}
		// reset player's position
		player.setPosition(0, 0);
	}

	private void gotoFado() {
		leaveCurrentZone();
		fado.add(player);
		assertEquals("0_fado_city", player.getZone().getName());
	}

	private void gotoPawnShop() {
		leaveCurrentZone();
		pawnShop.add(player);
		assertEquals("int_deniran_pawn_shop", player.getZone().getName());
	}

	private void gotoAthor() {
		leaveCurrentZone();
		athor.add(player);
		assertEquals("0_athor_island", player.getZone().getName());
	}

	private void testWithoutQuestLoaded() {
		Engine en;
		for (final SpeakerNPC npc: Arrays.asList(ari, emma)) {
			assertEquals("love", npc.getIdea());

			en = npc.getEngine();
			assertEquals(ConversationStates.IDLE, en.getCurrentState());
			en.step(player, "hi");
			assertEquals(ConversationStates.IDLE, en.getCurrentState());
		}

		en = sawyer.getEngine();
		en.step(player, "hi");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals("Welcome to the Deniran Pawn Shop.", getReply(sawyer));
		en.step(player, "offer");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals(
			"Please check the blackboard for a list of items that I buy.",
			getReply(sawyer));
		en.step(player, "bye");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals("Bye.", getReply(sawyer));
	}

	private void testWithQuestLoaded() {
		loadCachedQuests();

		// make sure Sawyer &amp; metal detector are working
		getMetalDetector();

		final Engine en = ari.getEngine();

		assertEquals(0, player.getLevel());
		assertFalse(player.hasFeature("keyring"));
		//assertFalse(player.hasVisitedZone("0_athor_island"));
		assertFalse(player.hasVisitedZone(athor));

		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		en.step(player, "hi");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals("Hi there!", getReply(ari));
		en.step(player, "quest");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals(
			"I don't think you have the experience to help me. Come back when"
				+ " you are stronger.",
			getReply(ari));

		player.setLevel(50);
		assertEquals(50, player.getLevel());

		en.step(player, "quest");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals(
			"I don't think you have the experience to help me. Maybe if you knew"
				+ " more about keyrings.",
			getReply(ari));

		player.setFeature("keyring", "2 4");
		assertTrue(player.hasFeature("keyring"));
		assertEquals("2 4", player.getFeature("keyring"));

		en.step(player, "quest");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals(
			"I don't think you have the experience to help me. Maybe if you were"
				+ " more familiar with Athor island.",
			getReply(ari));
		en.step(player, "bye");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals("Bye.", getReply(ari));

		gotoAthor();
		gotoFado();
		assertTrue(player.hasVisitedZone("0_athor_island"));

		int karma = (int) player.getKarma();

		en.step(player, "hi");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals(
			"Hey! You look like an experienced adventurer. Perhaps you could help me with a #task.",
			getReply(ari));
		en.step(player, "quest");
		assertEquals(ConversationStates.QUEST_OFFERED, en.getCurrentState());
		assertEquals(
			"I have lost my engagement ring, and I am too embarrassed to tell "
				+ emma.getName() + ". Would you help me?",
			getReply(ari));
		en.step(player, "no");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals(
			"I don't want to leave " + emma.getName()
				+ "'s side. I hope I can find someone to help me.",
			getReply(ari));
		assertEquals(karma - 15, (int) player.getKarma());

		karma = (int) player.getKarma();

		en.step(player, "hi");
		en.step(player, "quest");
		en.step(player, "yes");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals(
			"Thank you so much! I lost my ring while visiting Athor Island."
				+ " Please let me know when you find it. And don't say anything to "
				+ emma.getName() + ".",
			getReply(ari));
		assertTrue(player.hasQuest(slot));
		//~ assertEquals(karma + 15, (int) player.getKarma());

		karma = (int) player.getKarma();

		en.step(player, "hi");
		assertEquals(ConversationStates.QUESTION_1, en.getCurrentState());
		assertEquals(
			"Did you find my ring? If there is something I can do to #help, please let me know.",
			getReply(ari));
		// player does not have ring
		en.step(player, "no");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals("Please keep looking.", getReply(ari));
		en.step(player, "hi");
		en.step(player, "yes");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals("You don't have my ring. Please, keep looking.", getReply(ari));
		en.step(player, "hi");
		en.step(player, "help");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals(
			"I may have dropped it while walking along the beach. If that is the case,"
				+ " you may need something to dig in the sand or something that can"
				+ " detect metal. Maybe a local pawn shop has a tool you could use.",
			getReply(ari));

		// try to give Ari a fake ring
		final Item fakeRing = entityMan.getItem("engagement ring");
		player.equip("bag", fakeRing);
		assertEquals(1, player.getAllEquipped("engagement ring").size());
		en.step(player, "hi");
		en.step(player, "yes");
		assertEquals("You don't have my ring. Please, keep looking.", getReply(ari));
		assertEquals(ConversationStates.IDLE, en.getCurrentState());

		player.drop("engagement ring");
		assertFalse(player.isEquipped("engagement ring"));

		// ATTENDING state does not occur in game while quest is active
		en.step(player, "hi");
		en.setCurrentState(ConversationStates.ATTENDING);
		en.step(player, "quest");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals("You are already helping me find my engagement ring.", getReply(ari));
		en.step(player, "bye");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());

		final String[] coords = player.getQuest(slot).split(";");

		// get ring
		searchForRing();

		// have ring but tell Ari is lost
		en.step(player, "hi");
		en.step(player, "lost");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals(
			"You lost my lost engagement ring? That's kind of ironic."
				+ " I bet you're teasing me.",
			getReply(ari));
		assertEquals("found_ring", player.getQuest(slot, 0));

		// truly lost ring
		player.dropWithItemdata("engagement ring", "Ari's ring");
		assertFalse(player.isEquipped("engagement ring"));
		en.step(player, "hi");
		en.step(player, "lost");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals(
			"You lost my lost engagement ring? That's kind of ironic."
				+ " I bet you dropped it near where you found it."
				+ " Check there again.",
			getReply(ari));

		// quest slot should have been reset to same position
		assertEquals(coords[0], player.getQuest(slot, 0));
		assertEquals(coords[1], player.getQuest(slot, 1));

		// get ring again
		searchForRing();

		// finish quest
		en.step(player, "hi");
		en.step(player, "yes");
		//en.step(player, "done");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals(
			"Thank you so much! As a reward, I will give you this keyring. It is larger than the one you have.",
			getReply(ari));
		assertEquals("done", player.getQuest(slot, 0));
		assertFalse(player.isEquipped("engagement ring"));
		assertEquals(karma + 50, (int) player.getKarma());
		assertEquals("3 4", player.getFeature("keyring"));
	}

	private void searchForRing() {
		gotoAthor();

		assertNotEquals("found_ring", player.getQuest(slot, 0));

		final MetalDetector detector = (MetalDetector) player.getFirstEquipped("metal detector");
		assertNotNull(detector);

		assertEquals(0, player.getX());
		assertEquals(0, player.getY());

		// did not find ring
		detector.onUsed(player);
		assertFalse(player.isEquipped("engagement ring"));
		assertNotEquals("found_ring", player.getQuest(slot, 0));

		// go to coordinates where ring can be found
		player.setPosition(
			Integer.parseInt(player.getQuest(slot, 0)),
			Integer.parseInt(player.getQuest(slot, 1)));

		// found ring
		detector.onUsed(player);
		assertTrue(player.isEquipped("engagement ring"));
		assertEquals("found_ring", player.getQuest(slot, 0));

		final Item ring = player.getFirstEquipped("engagement ring");
		assertEquals(player.getName(), ring.getBoundTo());
		assertEquals("Ari's ring", ring.getItemData());

		// put player back in Fado City
		gotoFado();
	}

	private void checkMetalDetectorNotOnShelf() {
		assertNull(pawnShop.getEntityAt(19, 5));
	}

	private void checkMetalDetectorOnShelf() {
		final Entity itemOnShelf = pawnShop.getEntityAt(19, 5);
		assertNotNull(itemOnShelf);
		assertTrue(itemOnShelf instanceof MetalDetector);
		assertEquals(sawyer.getName(), ((MetalDetector) itemOnShelf).getBoundTo());
	}

	private void removeMetalDetectorFromShelf() {
		final Entity itemOnShelf = pawnShop.getEntityAt(19, 5);
		if (itemOnShelf != null) {
			pawnShop.remove(itemOnShelf);
		}

		checkMetalDetectorNotOnShelf();
	}

	private void getMetalDetector() {
		gotoPawnShop();

		final Engine en = sawyer.getEngine();
		assertEquals(ConversationStates.IDLE, en.getCurrentState());

		final String pawnSlot = "sawyer_metal_detector";

		assertFalse(player.hasQuest(pawnSlot));
		assertFalse(player.isEquipped("metal detector"));
		assertFalse(player.isEquipped("mithril legs"));

		en.step(player, "hi");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals("Welcome to the Deniran Pawn Shop.", getReply(sawyer));
		en.step(player, "help");
		assertEquals(
			"If you want to pawn something, check my blackboard for a list of what I buy.",
			getReply(sawyer));
		en.step(player, "offer");
		assertEquals(
			"Please check the blackboard for a list of items that I buy."
				+ " I also have some items that I don't mind #loaning out.",
			getReply(sawyer));
		en.step(player, "return");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals("Return what? I haven't loaned anything to you.", getReply(sawyer));
		en.step(player, "borrow");
		assertEquals(ConversationStates.QUESTION_1, en.getCurrentState());
		assertEquals(
			"So, you want to borrow my metal detector? Well, I don't lend things out"
				+ " without some form of collateral. What would you like to leave"
				+ " behind?",
			getReply(sawyer));
		en.step(player, "no");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals("Okay then. What else can I help you with?", getReply(sawyer));
		en.step(player, "borrow");
		en.step(player, "cheese");
		assertEquals(ConversationStates.QUESTION_1, en.getCurrentState());
		assertEquals("Hmmm, I'm not interested in that. What else you got?", getReply(sawyer));
		en.step(player, "mithril legs");
		assertEquals(ConversationStates.QUESTION_1, en.getCurrentState());
		assertEquals(
			"You're not even carrying one of those. Come on, what do you have?",
			getReply(sawyer));

		// test borrowing when detector is on shelf
		borrowFromShelf(en, pawnSlot);
		// test borrowing when detector not on shelf
		borrowExtra(en, pawnSlot);

		// put player back in Fado City
		gotoFado();
	}

	/**
	 * Tests borrowing metal detector from Sawyer when item
	 * is still on shelf.
	 */
	private void borrowFromShelf(final Engine en, final String pawnSlot) {
		assertFalse(player.hasQuest(pawnSlot));
		assertFalse(player.isEquipped("metal detector"));

		// check that metal detector is on shelf
		checkMetalDetectorOnShelf();

		final String legsInfo = "foo bar";
		final Item legs = entityMan.getItem("mithril legs");
		legs.setBoundTo(player.getName());
		legs.setItemData(legsInfo);

		player.equip("bag", legs);
		assertEquals(1, player.getAllEquipped("mithril legs").size());

		en.step(player, "mithril legs");
		// player walked away
		assertNull(sawyer.getAttending());
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertEquals(
			"Where did you go? Oh well, guess I'll just put this back on the shelf.",
			getReply(sawyer));

		en.step(player, "hi");
		sawyer.setAttending(player);
		assertEquals(player, sawyer.getAttending());
		en.step(player, "borrow");
		en.step(player, "mithril legs");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals(
			"Okay, here you go. My metal detector for your mithril legs"
				+ ". Be careful with it. If it gets lost, you won't be able to"
				+ " #return it and I will keep your mithril legs. Anything"
				+ " else I can help you with?",
			getReply(sawyer));
		en.step(player, "bye");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		sawyer.setAttending(null);
		assertNull(sawyer.getAttending());

		assertFalse(player.isEquipped("mithril legs"));
		assertEquals(1, player.getAllEquipped("metal detector").size());
		// make sure bound &amp; itemdata data are stored in quest slot
		assertEquals("mithril legs", player.getQuest(pawnSlot, 0));
		assertEquals(player.getName(), player.getQuest(pawnSlot, 1));
		assertEquals(legsInfo, player.getQuest(pawnSlot, 2));

		// check that detector was removed from shelf
		checkMetalDetectorNotOnShelf();

		final MetalDetector detector = (MetalDetector) player.getFirstEquipped("metal detector");
		assertNotNull(detector);
		assertEquals(player.getName(), detector.getBoundTo());
		// make sure bound &amp; itemdata data are stored in item itemdata
		final String[] dinfo = detector.getItemData().split(";");
		assertEquals(sawyer.getName(), dinfo[0]);
		assertEquals("mithril legs", dinfo[1]);
		assertEquals(player.getName(), dinfo[2]);
		assertEquals(legsInfo, dinfo[3]);

		// returning metal detector

		en.step(player, "hi");
		en.step(player, "return");
		assertEquals(ConversationStates.QUESTION_1, en.getCurrentState());
		assertEquals("You want to return my metal detector?", getReply(sawyer));
		en.step(player, "no");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals("Okay. What else can I help you with?", getReply(sawyer));

		// try to return metal detector when not equipped
		player.drop(detector);
		assertFalse(player.isEquipped("metal detector"));
		en.step(player, "return");
		en.step(player, "yes");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals("You aren't even carryng a metal detector.", getReply(sawyer));

		// try to return a metal detector that doesn't belong to Sawyer
		final Item fakeDetector = entityMan.getItem("metal detector");
		player.equip("bag", fakeDetector);
		assertEquals(1, player.getAllEquipped("metal detector").size());
		en.step(player, "return");
		en.step(player, "yes");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals("This isn't mine. Take it back. I want MY metal detector.", getReply(sawyer));
		player.drop(fakeDetector);
		assertFalse(player.isEquipped("metal detector"));

		// return the real metal detector
		player.equip("bag", detector);
		en.step(player, "return");
		en.step(player, "yes");
		assertEquals(ConversationStates.ATTENDING, en.getCurrentState());
		assertEquals(
			"Okay. Here is your mithril legs. Good as new. Is there"
				+ " anything else I can help you with?",
			getReply(sawyer));
		en.step(player, "bye");
		assertEquals(ConversationStates.IDLE, en.getCurrentState());
		assertFalse(player.isEquipped("metal detector"));
		assertFalse(player.hasQuest(pawnSlot));

		// check that metal detector was returned to shelf
		checkMetalDetectorOnShelf();

		// make sure returned item retains bound &amp; itemdata information
		final Item returnedLegs = player.getFirstEquipped("mithril legs");
		assertNotNull(returnedLegs);
		assertEquals(player.getName(), returnedLegs.getBoundTo());
		assertEquals(legsInfo, returnedLegs.getItemData());
	}

	/**
	 * Tests borrowing metal detector from Sawyer when item
	 * has been previously borrowed by another player.
	 */
	private void borrowExtra(final Engine en, final String pawnSlot) {
		assertFalse(player.hasQuest(pawnSlot));
		assertFalse(player.isEquipped("metal detector"));

		// make sure metal detector was removed from shelf
		removeMetalDetectorFromShelf();

		player.equip("head", entityMan.getItem("black helmet"));
		assertEquals(1, player.getAllEquipped("black helmet").size());

		en.step(player, "hi");
		en.step(player, "borrow");
		// clear replies cache
		getReply(sawyer);

		// FIXME: how do we simulate waiting for Sawyer to finish thinking?
		// equip with metal detector so rest of test doesn't fail
		player.equip("bag", entityMan.getItem("metal detector"));

		/*
		en.step(player, "black helmet");
		assertEquals(ConversationStates.BUSY, en.getCurrentState());

		List&lt;String&gt; replies = getOrderedReplies(sawyer);
		assertEquals(2, replies.size());
		assertEquals("Hmmmm. I guess I forgot that I already loaned it out...", replies.get(0));
		assertEquals("!me is thinking.", replies.get(1));

		SingletonRepository.getTurnNotifier().notifyInTurns(0, new TurnListener() {
			public void onTurnReached(final int currentTurn) {
				en.step(player, "black helmet");
				assertEquals(ConversationStates.BUSY, en.getCurrentState());

				final List&lt;String&gt; replies = getOrderedReplies(sawyer);
				assertEquals(2, replies.size());
				assertEquals("Hmmmm. I guess I forgot that I already loaned it out...", replies.get(0));
				assertEquals("!me is thinking.", replies.get(1));
			}
		});

		final long startTime = System.currentTimeMillis();

		int reports = 0;
		int elapsed = 0;
		System.out.println("Sawyer is thinking ...");
		while (elapsed &lt; 16) {
			// wait for Sawyer to finish thinking

			if (elapsed &gt;= 5 &amp;&amp; reports == 0) {
				System.out.println("Sawyer is still thinking ...");
				reports++;
			} else if (elapsed &gt;= 10 &amp;&amp; reports == 1) {
				System.out.println("Sawyer is almost done thinking ...");
				reports++;
			}

			elapsed = (int) ((System.currentTimeMillis() - startTime) / 1000);
		}

		final List&lt;String&gt; replies = getOrderedReplies(sawyer);
		assertEquals(2, replies.size());
		assertEquals("Oh! That's right, I keep a spare just in case.", replies.get(0));
		assertEquals(
			"Okay, here you go. My metal detector for your black helmet."
				+ " Be careful with it. If it gets lost, you won't be able to"
				+ " #return it and I will keep your black helmet. Anything"
				+ " else I can help you with?",
			replies.get(1));
		*/

		// TODO: player dropped item while Sawyer was thinking
	}
}
</pre></body></html>