diff --git a/data/conf/zones/deniran.xml b/data/conf/zones/deniran.xml
index 043d84d280c..9f2390acdf7 100644
--- a/data/conf/zones/deniran.xml
+++ b/data/conf/zones/deniran.xml
@@ -1429,6 +1429,7 @@ Entrance fee is 10 money
+
diff --git a/doc/CHANGES.txt b/doc/CHANGES.txt
index 6e94062f9c0..784b3d242c2 100644
--- a/doc/CHANGES.txt
+++ b/doc/CHANGES.txt
@@ -10,6 +10,7 @@ Changelog
- Mayor Chalmers won't accept experimental sandwiches for Daily Item Quest
- touched up and added equipment to many entity sprites
- new player selectable hair
+- new NPC Tinny
*chat*
- new offers with Harold are announced in chat
diff --git a/src/games/stendhal/server/maps/deniran/cityinterior/tannery/LeatherCrafterNPC.java b/src/games/stendhal/server/maps/deniran/cityinterior/tannery/LeatherCrafterNPC.java
new file mode 100644
index 00000000000..9f18250dc63
--- /dev/null
+++ b/src/games/stendhal/server/maps/deniran/cityinterior/tannery/LeatherCrafterNPC.java
@@ -0,0 +1,93 @@
+/***************************************************************************
+ * Copyright © 2024 - Faiumoni e. V. *
+ ***************************************************************************
+ ***************************************************************************
+ * *
+ * 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 games.stendhal.server.maps.deniran.cityinterior.tannery;
+
+import java.awt.Color;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import games.stendhal.common.constants.SkinColor;
+import games.stendhal.server.core.config.ZoneConfigurator;
+import games.stendhal.server.core.engine.StendhalRPZone;
+import games.stendhal.server.core.pathfinder.FixedPath;
+import games.stendhal.server.core.pathfinder.Node;
+import games.stendhal.server.entity.CollisionAction;
+import games.stendhal.server.entity.npc.ChatCondition;
+import games.stendhal.server.entity.npc.ConversationPhrases;
+import games.stendhal.server.entity.npc.ConversationStates;
+import games.stendhal.server.entity.npc.SpeakerNPC;
+import games.stendhal.server.entity.npc.condition.NotCondition;
+import games.stendhal.server.entity.npc.condition.QuestRegisteredCondition;
+
+
+public class LeatherCrafterNPC implements ZoneConfigurator {
+
+ @Override
+ public void configureZone(StendhalRPZone zone, Map attributes) {
+ zone.add(buildNPC());
+ }
+
+ private SpeakerNPC buildNPC() {
+ final SpeakerNPC npc = new SpeakerNPC("Tinny");
+ npc.setDescription("You see Tinny, the leather crafter.");
+
+ buildOutfit(npc);
+ buildPath(npc);
+ buildDialogue(npc);
+
+ return npc;
+ }
+
+ private void buildOutfit(final SpeakerNPC npc) {
+ npc.setOutfit("body=0,head=0,eyes=3,hair=57,dress=968,mask=5,detail=16");
+ npc.setOutfitColor("body", SkinColor.LIGHT);
+ npc.setOutfitColor("eyes", Color.BLUE);
+ }
+
+ private void buildPath(final SpeakerNPC npc) {
+ final List nodes = new LinkedList<>();
+ nodes.add(new Node(30, 4));
+ nodes.add(new Node(20, 4));
+ nodes.add(new Node(20, 13));
+ nodes.add(new Node(30, 13));
+ npc.setPath(new FixedPath(nodes, true));
+ npc.setPosition(30, 4);
+ npc.setCollisionAction(CollisionAction.STOP);
+ }
+
+ private void buildDialogue(final SpeakerNPC npc) {
+ npc.addGreeting("Hello, how can I help you?");
+ npc.addGoodbye("Goodbye.");
+ npc.addJob("I am a leather crafter. I recently completed my apprenticeship under Skinner and"
+ + " will one day take over responsibility of the tannery.");
+
+ final String questSlot = "items_for_tinny";
+ final ChatCondition questNotRegistered = new NotCondition(new QuestRegisteredCondition(questSlot));
+
+ npc.add(
+ ConversationStates.ATTENDING,
+ ConversationPhrases.HELP_MESSAGES,
+ questNotRegistered,
+ ConversationStates.ATTENDING,
+ "If you are interested in a pouch to carry your money in, speak to Skinner.",
+ null);
+
+ npc.add(
+ ConversationStates.ATTENDING,
+ ConversationPhrases.QUEST_MESSAGES,
+ questNotRegistered,
+ ConversationStates.ATTENDING,
+ "There is nothing I need help with at this time.",
+ null);
+ }
+}
diff --git a/tests/games/stendhal/server/maps/deniran/cityinterior/tannery/LeatherCrafterNPCTest.java b/tests/games/stendhal/server/maps/deniran/cityinterior/tannery/LeatherCrafterNPCTest.java
new file mode 100644
index 00000000000..0dc8d3d3dfa
--- /dev/null
+++ b/tests/games/stendhal/server/maps/deniran/cityinterior/tannery/LeatherCrafterNPCTest.java
@@ -0,0 +1,72 @@
+/***************************************************************************
+ * Copyright © 2024 - Faiumoni e. V. *
+ ***************************************************************************
+ ***************************************************************************
+ * *
+ * 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 games.stendhal.server.maps.deniran.cityinterior.tannery;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static utilities.SpeakerNPCTestHelper.getReply;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import games.stendhal.server.core.engine.SingletonRepository;
+import games.stendhal.server.entity.npc.SpeakerNPC;
+import games.stendhal.server.entity.npc.fsm.Engine;
+import games.stendhal.server.entity.player.Player;
+import utilities.PlayerTestHelper;
+import utilities.QuestHelper;
+import utilities.ZonePlayerAndNPCTestImpl;
+
+
+public class LeatherCrafterNPCTest extends ZonePlayerAndNPCTestImpl {
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ QuestHelper.setUpBeforeClass();
+ setupZone("testzone");
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ setZoneForPlayer("testzone");
+ setNpcNames("Tinny");
+ addZoneConfigurator(new LeatherCrafterNPC(), "testzone");
+
+ super.setUp();
+ }
+
+ @Test
+ public void testDialogue() {
+ final Player player = PlayerTestHelper.createPlayer("player");
+ final SpeakerNPC npc = SingletonRepository.getNPCList().get("Tinny");
+
+ assertThat(player, notNullValue());
+ assertThat(npc, notNullValue());
+
+ final Engine en = npc.getEngine();
+ en.step(player, "hi");
+ assertThat(getReply(npc), is("Hello, how can I help you?"));
+ en.step(player, "job");
+ assertThat(getReply(npc), is("I am a leather crafter. I recently completed my apprenticeship"
+ + " under Skinner and will one day take over responsibility of the tannery."));
+ en.step(player, "help");
+ assertThat(getReply(npc), is("If you are interested in a pouch to carry your money in, speak to"
+ + " Skinner."));
+ en.step(player, "task");
+ assertThat(getReply(npc), is("There is nothing I need help with at this time."));
+ en.step(player, "bye");
+ assertThat(getReply(npc), is("Goodbye."));
+ }
+}