From 74b429083b4f10bab82641fe46cf00f6f247dec6 Mon Sep 17 00:00:00 2001 From: Cadyyan Date: Thu, 12 Feb 2026 19:06:01 -0600 Subject: [PATCH] Add ability to create talisman tiaras Talisman tiaras had their general mechanics implemented already but you couldn't actually craft them at the altar yet. This adds the ability to create talisman tiaras by taking a blank tiara and a talisman to a rune altar and using the tiara on the altar. Added an automated test for this behavior as well as the requisite experience configuration to the TOML files. Experience per craft were taken from the OSRS wiki. --- .../runecrafting/runecrafting.items.toml | 12 ++++ .../engine/data/definition/ItemDefinitions.kt | 1 + .../engine/data/definition/data/Tiara.kt | 42 ++++++++++++ .../content/skill/runecrafting/Tiaras.kt | 55 ++++++++++++++++ .../content/skill/runecrafting/TiaraTest.kt | 64 +++++++++++++++++++ 5 files changed, 174 insertions(+) create mode 100644 engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/data/Tiara.kt create mode 100644 game/src/main/kotlin/content/skill/runecrafting/Tiaras.kt create mode 100644 game/src/test/kotlin/content/skill/runecrafting/TiaraTest.kt diff --git a/data/skill/runecrafting/runecrafting.items.toml b/data/skill/runecrafting/runecrafting.items.toml index ac1bf2df3c..4afa6f95d3 100644 --- a/data/skill/runecrafting/runecrafting.items.toml +++ b/data/skill/runecrafting/runecrafting.items.toml @@ -182,6 +182,7 @@ limit = 100 weight = 1.0 slot = "Hat" type = "HairMid" +talisman_tiara = { xp = 25.0, level = 1 } examine = "A tiara infused with the properties of air." kept = "Wilderness" @@ -193,6 +194,7 @@ id = 5529 price = 14 limit = 100 weight = 1.0 +talisman_tiara = { xp = 27.5, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of the mind." @@ -206,6 +208,7 @@ id = 5531 price = 2481 limit = 100 weight = 1.0 +talisman_tiara = { xp = 30.0, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of water." @@ -219,6 +222,7 @@ id = 5533 price = 3 limit = 100 weight = 1.0 +talisman_tiara = { xp = 37.5, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of the body." @@ -232,6 +236,7 @@ id = 5535 price = 4 limit = 100 weight = 1.0 +talisman_tiara = { xp = 32.5, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of the earth." @@ -245,6 +250,7 @@ id = 5537 price = 10 limit = 100 weight = 1.0 +talisman_tiara = { xp = 35.0, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of fire." @@ -258,6 +264,7 @@ id = 5539 price = 4 limit = 100 weight = 1.0 +talisman_tiara = { xp = 40.0, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of the cosmos." @@ -270,6 +277,7 @@ id = 5541 price = 8 limit = 100 weight = 1.0 +talisman_tiara = { xp = 45.0, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of nature." @@ -282,6 +290,7 @@ id = 5543 price = 5 limit = 100 weight = 1.0 +talisman_tiara = { xp = 42.5, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of chaos." @@ -294,6 +303,7 @@ id = 5545 price = 3 limit = 100 weight = 1.0 +talisman_tiara = { xp = 47.5, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of law." @@ -306,6 +316,7 @@ id = 5547 price = 213 limit = 100 weight = 1.0 +talisman_tiara = { xp = 50.0, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of death." @@ -318,6 +329,7 @@ id = 5549 price = 11900 limit = 100 weight = 1.0 +talisman_tiara = { xp = 52.5, level = 1 } slot = "Hat" type = "HairMid" examine = "A tiara infused with the properties of blood." diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemDefinitions.kt index 69aae8cea5..b6d533f500 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/ItemDefinitions.kt @@ -82,6 +82,7 @@ object ItemDefinitions : DefinitionsDecoder { "jewellery" -> extras[key] = Jewellery(this) "silver_jewellery" -> extras[key] = Silver(this) "runecrafting" -> extras[key] = Rune(this) + "talisman_tiara" -> extras[key] = Tiara(this) "cleaning" -> extras[key] = Cleaning(this) "fletch_dart" -> extras[key] = FletchDarts(this) "fletch_bolts" -> extras[key] = FletchBolts(this) diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/data/Tiara.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/data/Tiara.kt new file mode 100644 index 0000000000..c19f15d5f4 --- /dev/null +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/data/Tiara.kt @@ -0,0 +1,42 @@ +package world.gregs.voidps.engine.data.definition.data + +import world.gregs.config.ConfigReader + +data class Tiara( + val xp: Double = 0.0, + val level: Int = 1, +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Tiara + + if (xp != other.xp) return false + return level == other.level + } + + override fun hashCode(): Int { + var result = xp.hashCode() + result = 31 * result + level + + return result + } + + companion object { + operator fun invoke(reader: ConfigReader): Tiara { + var xp = 0.0 + var level = 1 + + while (reader.nextEntry()) { + when (val key = reader.key()) { + "xp" -> xp = reader.double() + "level" -> level = reader.int() + else -> throw IllegalArgumentException("Unexpected key: '$key' ${reader.exception()}") + } + } + + return Tiara(xp = xp, level = level) + } + } +} \ No newline at end of file diff --git a/game/src/main/kotlin/content/skill/runecrafting/Tiaras.kt b/game/src/main/kotlin/content/skill/runecrafting/Tiaras.kt new file mode 100644 index 0000000000..2f4a22181e --- /dev/null +++ b/game/src/main/kotlin/content/skill/runecrafting/Tiaras.kt @@ -0,0 +1,55 @@ +package content.skill.runecrafting + +import com.github.michaelbull.logging.InlineLogger +import world.gregs.voidps.cache.definition.data.ItemDefinition +import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.message +import world.gregs.voidps.engine.client.variable.start +import world.gregs.voidps.engine.data.definition.ItemDefinitions +import world.gregs.voidps.engine.data.definition.data.Tiara +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.character.player.skill.exp.exp +import world.gregs.voidps.engine.entity.character.sound +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.engine.inv.transact.TransactionError +import world.gregs.voidps.engine.inv.transact.operation.AddItem.add +import world.gregs.voidps.engine.inv.transact.operation.RemoveItem.remove + +class Tiaras : Script { + + val logger = InlineLogger() + + init { + itemOnObjectOperate("tiara", "*_altar") { (target) -> + val id = target.id.replace("_altar", "_tiara") + bindTiara(this, id, ItemDefinitions.get(id)) + } + } + + fun Tiaras.bindTiara(player: Player, id: String, itemDefinition: ItemDefinition) { + val tiara: Tiara = itemDefinition.getOrNull("talisman_tiara") ?: return + player.softTimers.start("runecrafting") + val tiaraId = "tiara" + val talismanId = id.replace("_tiara", "_talisman") + player.inventory.transaction { + remove(tiaraId, 1) + remove(talismanId, 1) + add(id, 1) + } + player.start("movement_delay", 3) + when (player.inventory.transaction.error) { + is TransactionError.Deficient, is TransactionError.Invalid -> { + player.message("You don't have a talisman to bind.") + } + TransactionError.None -> { + player.exp(Skill.Runecrafting, tiara.xp) + player.anim("bind_runes") + player.gfx("bind_runes") + player.sound("bind_runes") + } + else -> logger.warn { "Error binding talisman with tiara $player $tiara ${player.levels.get(Skill.Runecrafting)} $talismanId" } + } + player.softTimers.stop("runecrafting") + } +} diff --git a/game/src/test/kotlin/content/skill/runecrafting/TiaraTest.kt b/game/src/test/kotlin/content/skill/runecrafting/TiaraTest.kt new file mode 100644 index 0000000000..55e86493a4 --- /dev/null +++ b/game/src/test/kotlin/content/skill/runecrafting/TiaraTest.kt @@ -0,0 +1,64 @@ +package content.skill.runecrafting + +import WorldTest +import content.entity.obj.ObjectTeleports +import itemOnObject +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DynamicTest.dynamicTest +import org.junit.jupiter.api.TestFactory +import org.koin.test.get +import world.gregs.voidps.engine.entity.character.player.skill.Skill +import world.gregs.voidps.engine.entity.obj.GameObjects +import world.gregs.voidps.engine.inv.add +import world.gregs.voidps.engine.inv.inventory +import world.gregs.voidps.type.Tile + +class TiaraTest : WorldTest() { + + private lateinit var teleports: ObjectTeleports + + @BeforeEach + fun setup() { + teleports = get() + } + + @TestFactory + fun `Bind tiara with a talisman at an altar`() = altars.map { (type, _, altarTile) -> + dynamicTest("Craft $type tiara") { + val tile = teleports.get("${type}_altar_ruins_enter", "Enter").first().to + val player = createPlayer(tile) + player.inventory.add("tiara", "${type}_talisman") + + val altar = GameObjects.find(altarTile, "${type}_altar") + player.itemOnObject(altar, 0) + tick(1) + tickIf { player.visuals.moved } + + assertFalse(player.inventory.contains("tiara")) + assertFalse(player.inventory.contains("${type}_talisman")) + assertTrue(player.inventory.contains("${type}_tiara")) + assertTrue(player.experience.get(Skill.Runecrafting) > 0) + } + } + + companion object { + internal data class Altar(val type: String, val ruinsTile: Tile, val altarTile: Tile) + + internal val altars = listOf( + Altar("air", Tile(3126, 3404), Tile(2843, 4833)), + Altar("water", Tile(3184, 3164), Tile(3483, 4835)), + Altar("earth", Tile(3305, 3473), Tile(2657, 4840)), + Altar("fire", Tile(3312, 3254), Tile(2584, 4837)), + Altar("mind", Tile(2981, 3513), Tile(2785, 4840)), + Altar("body", Tile(3052, 3444), Tile(2522, 4839)), + Altar("cosmic", Tile(2407, 4376), Tile(2141, 4832)), + Altar("law", Tile(2857, 3380), Tile(2463, 4831)), + Altar("nature", Tile(2868, 3018), Tile(2399, 4840)), + Altar("chaos", Tile(3059, 3590), Tile(2270, 4841)), + Altar("death", Tile(1860, 4638), Tile(2204, 4835)), + Altar("blood", Tile(3560, 9780), Tile(2461, 4894, 1)), + ) + } +}