diff --git a/data/area/misthalin/lumbridge/lumbridge.npc-spawns.toml b/data/area/misthalin/lumbridge/lumbridge.npc-spawns.toml index 7079fe9b0a..b253704f63 100644 --- a/data/area/misthalin/lumbridge/lumbridge.npc-spawns.toml +++ b/data/area/misthalin/lumbridge/lumbridge.npc-spawns.toml @@ -145,7 +145,7 @@ spawns = [ { id = "shop_assistant_lumbridge", x = 3214, y = 3243 }, { id = "bob", x = 3228, y = 3203 }, { id = "banker_3", x = 3208, y = 3222, level = 2, direction = "SOUTH" }, - { id = "hans", x = 3223, y = 3225 }, + { id = "hans", x = 3219, y = 3222 }, { id = "giant_spider", x = 3249, y = 3249 }, { id = "giant_spider", x = 3241, y = 3241 }, { id = "giant_spider", x = 3250, y = 3239 }, diff --git a/data/area/misthalin/lumbridge/lumbridge.patrols.toml b/data/area/misthalin/lumbridge/lumbridge.patrols.toml new file mode 100644 index 0000000000..eb4284c82c --- /dev/null +++ b/data/area/misthalin/lumbridge/lumbridge.patrols.toml @@ -0,0 +1,16 @@ +[hans] +defaultmode = "patrol" +patrol0 = "0_50_50_19_22,10" +patrol1 = "0_50_50_21_22,0" +patrol2 = "0_50_50_21_12,0" +patrol3 = "0_50_50_20_12,0" +patrol4 = "0_50_50_18_10,0" +patrol5 = "0_50_50_18_8,0" +patrol6 = "0_50_50_17_8,0" +patrol7 = "0_50_50_14_5,0" +patrol8 = "0_50_50_2_5,0" +patrol9 = "0_50_50_2_32,0" +patrol10 = "0_50_50_3_33,0" +patrol11 = "0_50_50_7_33,0" +patrol12 = "0_50_50_10_30,0" +patrol13 = "0_50_50_19_30,0" diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/PatrolDefinitions.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/PatrolDefinitions.kt index f2d151edfa..d60580bce7 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/PatrolDefinitions.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/definition/PatrolDefinitions.kt @@ -42,7 +42,12 @@ class PatrolDefinitions { } points.add(Tile(x, y, level) to delay) } - else -> throw IllegalArgumentException("Unexpected key: '$key' ${exception()}") + "defaultmode" -> string() + else -> if (key.startsWith("patrol")) { + points.add(parseLegacyPatrolPoint(string())) + } else { + throw IllegalArgumentException("Unexpected key: '$key' ${exception()}") + } } } definitions[stringId] = PatrolDefinition(stringId = stringId, waypoints = points) @@ -54,4 +59,17 @@ class PatrolDefinitions { } return this } + + private fun parseLegacyPatrolPoint(value: String): Pair { + val parts = value.split(",", limit = 2) + val coords = parts[0].split("_") + require(coords.size == 5) { "Unexpected patrol point value: '$value'" } + val level = coords[0].toInt() + val regionX = coords[1].toInt() + val regionY = coords[2].toInt() + val localX = coords[3].toInt() + val localY = coords[4].toInt() + val delay = parts.getOrNull(1)?.toIntOrNull() ?: 0 + return Tile(regionX * 64 + localX, regionY * 64 + localY, level) to delay + } } diff --git a/game/src/main/kotlin/content/area/misthalin/lumbridge/castle/Hans.kt b/game/src/main/kotlin/content/area/misthalin/lumbridge/castle/Hans.kt index 638c58f8b0..550781b249 100644 --- a/game/src/main/kotlin/content/area/misthalin/lumbridge/castle/Hans.kt +++ b/game/src/main/kotlin/content/area/misthalin/lumbridge/castle/Hans.kt @@ -6,18 +6,46 @@ import content.entity.player.dialogue.Neutral import content.entity.player.dialogue.type.choice import content.entity.player.dialogue.type.npc import world.gregs.voidps.engine.Script +import world.gregs.voidps.engine.client.ui.dialogue +import world.gregs.voidps.engine.data.definition.PatrolDefinitions +import world.gregs.voidps.engine.entity.character.mode.Patrol import world.gregs.voidps.engine.entity.character.mode.Retreat +import world.gregs.voidps.engine.entity.character.npc.NPC +import world.gregs.voidps.engine.entity.character.player.Player +import world.gregs.voidps.engine.queue.queue -class Hans : Script { +class Hans( + private val patrols: PatrolDefinitions, +) : Script { init { + npcSpawn("hans") { + val patrol = patrols.get("hans") + if (patrol.waypoints.isNotEmpty()) { + set("hans_patrol_index", 0) + mode = Patrol(this, patrol.waypoints) + } + } + + npcMoved("hans") { + if (mode is Patrol) { + set("hans_patrol_index", get("patrol_index", 0)) + } + } + npcOperate("Talk-to", "hans") { (target) -> + var resumeFromNearest = false + var resumeDelay = 10 + scheduleResume(this, target, resumeDelay) { resumeFromNearest } npc("Hello. What are you doing here?") choice { option("I'm looking for whoever is in charge of this place.") { npc("Who, the Duke? He's in his study, on the first floor.") } option("I have come to kill everyone in this castle!") { + resumeFromNearest = true + resumeDelay = 15 + scheduleResume(this, target, resumeDelay) { resumeFromNearest } target.say("Help! Help!") target.mode = Retreat(target, this) } @@ -27,4 +55,34 @@ class Hans : Script { } } } + + private fun scheduleResume(player: Player, target: NPC, delay: Int, nearest: () -> Boolean) { + target.queue.clear("hans_resume_patrol") + target.queue("hans_resume_patrol", delay) { + if (player.dialogue != null) { + scheduleResume(player, target, 1, nearest) + return@queue + } + resumePatrol(target, nearest = nearest()) + } + } + + private fun resumePatrol(target: NPC, nearest: Boolean = false) { + val patrol = patrols.get("hans") + if (patrol.waypoints.isEmpty()) { + return + } + val size = patrol.waypoints.size + val baseIndex = if (nearest) { + patrol.waypoints.withIndex() + .minByOrNull { target.tile.distanceTo(it.value.first) } + ?.index ?: 0 + } else { + target["hans_patrol_index", 0].mod(size) + } + val resumeIndex = if (patrol.waypoints[baseIndex].first == target.tile) (baseIndex + 1).mod(size) else baseIndex + target.mode = Patrol(target, patrol.waypoints) + target.set("patrol_index", resumeIndex) + target.steps.clear() + } }