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/data/sprites/outfit/detail/016-blacksmith_hammer.xcf.bz2 b/data/sprites/outfit/detail/016-blacksmith_hammer.xcf.bz2
new file mode 100644
index 00000000000..4185a7995f3
Binary files /dev/null and b/data/sprites/outfit/detail/016-blacksmith_hammer.xcf.bz2 differ
diff --git a/data/sprites/outfit/detail/016-rear.png b/data/sprites/outfit/detail/016-rear.png
new file mode 100644
index 00000000000..ad4123a67d1
Binary files /dev/null and b/data/sprites/outfit/detail/016-rear.png differ
diff --git a/data/sprites/outfit/detail/016.png b/data/sprites/outfit/detail/016.png
new file mode 100644
index 00000000000..58a57781baa
Binary files /dev/null and b/data/sprites/outfit/detail/016.png differ
diff --git a/data/sprites/outfit/hair/057.png b/data/sprites/outfit/hair/057.png
new file mode 100644
index 00000000000..e446dec0868
Binary files /dev/null and b/data/sprites/outfit/hair/057.png differ
diff --git a/data/sprites/outfit/outfits.json b/data/sprites/outfit/outfits.json
index 392dfa86dcf..9bccb61fd79 100644
--- a/data/sprites/outfit/outfits.json
+++ b/data/sprites/outfit/outfits.json
@@ -1,5 +1,5 @@
{
"detail": {
- "rear": [1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15]
+ "rear": [1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
}
}
diff --git a/doc/CHANGES.txt b/doc/CHANGES.txt
index 5f864c97dff..784b3d242c2 100644
--- a/doc/CHANGES.txt
+++ b/doc/CHANGES.txt
@@ -9,6 +9,8 @@ Changelog
- added music to Athor Island
- 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/doc/sources/graphics-outfit.txt b/doc/sources/graphics-outfit.txt
index a4877d0a9b3..f14f33f2aed 100644
--- a/doc/sources/graphics-outfit.txt
+++ b/doc/sources/graphics-outfit.txt
@@ -27,19 +27,20 @@ data/sprites/outfit/body/
data/sprites/outfit/detail/
000 (empty)
-00[1-3] (balloon) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal, https://opengameart.org/node/101299
-004 (bone) Bart Kelsey; CC0; https://opengameart.org/node/3755
-005 (flag) Kimmo Rundelin (kiheru); CC BY-SA 3.0; Stendhal
-006 (cane) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
-007 (umbrella) Diligent Dodo (soniccuz), Jordan Irwin (AntumDeluge); CC BY-SA 3.0; https://sourceforge.net/p/arianne/patches/687/
-008 (sword) LordNeo; CC0; https://opengameart.org/node/10878
-009 (knife) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
-010 (spear) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
-011 (bow) Michael Williams (BizmasterStudios); CC BY 3.0; https://opengameart.org/node/66349
-012 (axe) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
-013 (axe) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
-014 (kite shield) Michael Williams (BizmasterStudios); CC BY 3.0; https://opengameart.org/node/64857
-015 (shield) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
+00[1-3] (balloon) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal, https://opengameart.org/node/101299
+004 (bone) Bart Kelsey; CC0; https://opengameart.org/node/3755
+005 (flag) Kimmo Rundelin (kiheru); CC BY-SA 3.0; Stendhal
+006 (cane) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
+007 (umbrella) Diligent Dodo (soniccuz), Jordan Irwin (AntumDeluge); CC BY-SA 3.0; https://sourceforge.net/p/arianne/patches/687/
+008 (sword) LordNeo; CC0; https://opengameart.org/node/10878
+009 (knife) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
+010 (spear) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
+011 (bow) Michael Williams (BizmasterStudios); CC BY 3.0; https://opengameart.org/node/66349
+012 (axe) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
+013 (axe) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
+014 (kite shield) Michael Williams (BizmasterStudios); CC BY 3.0; https://opengameart.org/node/64857
+015 (shield) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
+016 (blacksmith hammer) Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal, https://opengameart.org/node/82370
data/sprites/outfit/dress/
@@ -241,6 +242,7 @@ data/sprites/outfit/hair/
054 diamonddmgirl, Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal, https://opengameart.org/node/72198
055 Yamilian; CC BY-SA 3.0; https://opengameart.org/node/11445
056 Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
+057 Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
998 Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal
999 diamonddmgirl, bleutailfly, Jordan Irwin (AntumDeluge); OGA BY 3.0; Stendhal, https://opengameart.org/node/72198
diff --git a/src/games/stendhal/common/Outfits.java b/src/games/stendhal/common/Outfits.java
index 83d96d62956..02e080741eb 100644
--- a/src/games/stendhal/common/Outfits.java
+++ b/src/games/stendhal/common/Outfits.java
@@ -1,6 +1,6 @@
/* $Id$ */
/***************************************************************************
- * (C) Copyright 2003-2010 - Stendhal *
+ * (C) Copyright 2003-2024 - Stendhal *
***************************************************************************
***************************************************************************
* *
@@ -34,7 +34,7 @@ public class Outfits {
public static final int CLOTHES_OUTFITS = 65;
/** number of player selectable hair styles */
- public static final int HAIR_OUTFITS = 57;
+ public static final int HAIR_OUTFITS = 58;
/** number of player selectable body shapes */
public static final int BODY_OUTFITS = 3;
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/src/games/stendhal/server/maps/quests/ItemsForTinny.java b/src/games/stendhal/server/maps/quests/ItemsForTinny.java
new file mode 100644
index 00000000000..9203e1e7e54
--- /dev/null
+++ b/src/games/stendhal/server/maps/quests/ItemsForTinny.java
@@ -0,0 +1,47 @@
+/***************************************************************************
+ * 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.quests;
+
+import games.stendhal.server.entity.npc.quest.BringItemQuestBuilder;
+import games.stendhal.server.entity.npc.quest.QuestManuscript;
+import games.stendhal.server.maps.Region;
+
+
+public class ItemsForTinny implements QuestManuscript {
+
+ @Override
+ public BringItemQuestBuilder story() {
+ final BringItemQuestBuilder quest = new BringItemQuestBuilder();
+
+ quest.info()
+ .name("Items for Tinny")
+ .internalName("items_for_tinny")
+ .description("Tinny the leather crafter needs to restock his shelves.")
+ .region(Region.DENIRAN_CITY)
+ .questGiverNpc("Tinny");
+
+ quest.history();
+
+ quest.offer()
+ .respondToRequest("Well, as a matter of fact I do have a task for you. I am in need of some"
+ + " supplies. Would you be interested in helping me?")
+ .respondToReject("Ah well, maybe I can find another brave soul to help me.")
+ .respondToAccept("Good! There is a few things that I need to restock my shelves. I would"
+ + " reward you nicely, I do love working with leather.");
+
+ quest.task();
+
+ quest.complete();
+
+ return quest;
+ }
+}
diff --git a/src/js/stendhal/data/OutfitStore.ts b/src/js/stendhal/data/OutfitStore.ts
index 10fbe6abbf6..2ddbc69c055 100644
--- a/src/js/stendhal/data/OutfitStore.ts
+++ b/src/js/stendhal/data/OutfitStore.ts
@@ -1,5 +1,5 @@
/***************************************************************************
- * Copyright © 2003-2023 - Arianne *
+ * Copyright © 2003-2024 - Arianne *
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@@ -21,7 +21,7 @@ export class OutfitStore {
// player pickable layers
private count: {[key: string]: number} = {
"hat": 19,
- "hair": 57,
+ "hair": 58,
"mask": 9,
"eyes": 28,
"mouth": 5,
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."));
+ }
+}
diff --git a/tests/games/stendhal/server/maps/quests/ItemsForTinnyTest.java b/tests/games/stendhal/server/maps/quests/ItemsForTinnyTest.java
new file mode 100644
index 00000000000..501f627b765
--- /dev/null
+++ b/tests/games/stendhal/server/maps/quests/ItemsForTinnyTest.java
@@ -0,0 +1,18 @@
+/***************************************************************************
+ * 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.quests;
+
+import utilities.QuestHelper;
+
+
+public class ItemsForTinnyTest extends QuestHelper {
+}