From 89edf78e547ce795e95d3f5828ce18e1b0545260 Mon Sep 17 00:00:00 2001 From: Michael Veit Date: Mon, 18 Aug 2025 11:08:56 +0000 Subject: [PATCH] Changed most of the doors in Draynor Manor to not autowalk Implemented Draynor Manor chair NPCs (The ones that follow you around the house) --- Server/data/configs/door_configs.json | 6 + Server/data/configs/npc_spawns.json | 4 + .../draynor/handlers/DraynorManorChairNPC.kt | 141 ++++++++++++++++++ .../draynor/handlers/DraynorManorHouseZone.kt | 57 +++++++ 4 files changed, 208 insertions(+) create mode 100644 Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorChairNPC.kt create mode 100644 Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt diff --git a/Server/data/configs/door_configs.json b/Server/data/configs/door_configs.json index ec17288a2..4dd902862 100644 --- a/Server/data/configs/door_configs.json +++ b/Server/data/configs/door_configs.json @@ -1439,6 +1439,12 @@ "fence": "false", "metal": "false" }, + { + "id": "11470", + "replaceId": "11471", + "fence": "false", + "metal": "false" + }, { "id": "11483", "replaceId": "11708", diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index e152ca446..4c566b7dd 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -6991,6 +6991,10 @@ "npc_id": "3288", "loc_data": "{2987,3446,0,1,7}-{3034,3437,0,1,0}-" }, + { + "npc_id": "3293", + "loc_data": "{3113,3369,1,0,0}-{3122,3353,0,0,3}-{3124,3360,0,0,2}-{3115,3362,0,0,1}-" + }, { "npc_id": "3294", "loc_data": "{3014,3339,0,1,2}-" diff --git a/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorChairNPC.kt b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorChairNPC.kt new file mode 100644 index 000000000..423e57dec --- /dev/null +++ b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorChairNPC.kt @@ -0,0 +1,141 @@ +package content.region.misthalin.draynor.handlers + +import core.api.hasLineOfSight +import core.game.interaction.MovementPulse +import core.game.node.entity.Entity +import core.game.node.entity.impl.PulseType +import core.game.node.entity.npc.AbstractNPC +import core.game.node.entity.player.Player +import core.game.world.map.Location +import core.game.world.map.RegionManager +import core.game.world.map.path.Pathfinder +import core.plugin.Initializable + +/** + * Represents the Draynor Manor Chair NPC, these are the chairs that follow the player + * around the manor. + * + * @author Broseki + */ + +private const val DRAYNOR_MANOR_CHAIR_NPC_ID = 3293 +private const val FOLLOWING_DISTANCE = 5 +private const val STOP_FOLLOWING_DISTANCE = 30 + +@Initializable +class DraynorManorChairNPC(id: Int = DRAYNOR_MANOR_CHAIR_NPC_ID, location: Location? = null) : + AbstractNPC(id, location) { + + // The player this NPC is currently following, null if nobody is being followed + private var following: Player? = null + + override fun construct(id: Int, location: Location, vararg objects: Any): AbstractNPC { + return DraynorManorChairNPC(id, location) + } + + override fun handleTickActions() { + super.handleTickActions() + val closestPlayer = findClosestPlayer() + // If there is a player nearby, follow them + if (closestPlayer != null && following != closestPlayer) { + stopFollowing() + following = closestPlayer + follow(closestPlayer) + following?.let { face(it) } + } else { + // If we didn't find a player within `FOLLOWING_DISTANCE` + // but we are following a player, check that they are still + // within `STOP_FOLLOWING_DISTANCE`, if they are gone, stop + // trying to follow them. + following?.let { player -> + if (findDistanceToPlayer(player) > STOP_FOLLOWING_DISTANCE + || !player.isActive + || player.isInvisible) { + stopFollowing() + } else { + if (!pulseManager.hasPulseRunning()) { + follow(player) + } + face(player) + } + } + } + } + + /** + * Stops following `following` + */ + fun stopFollowing() { + following = null + resetWalk() + pulseManager.clear(PulseType.STANDARD) + } + + /** + * Finds the closest player to the current entity within `FOLLOWING_DISTANCE` + * that is currently in the chair's line of sight. + * + * @return The Player object representing the closest player, or null if there are no players nearby. + */ + fun findClosestPlayer(): Player? { + val players = RegionManager.getLocalPlayers(this, FOLLOWING_DISTANCE) + if (players.isEmpty()) { + return null + } + + var closestPlayer: Player? = null + var closestDistance = Double.MAX_VALUE + + for (player in players) { + // Make sure the chair does not try to start + // following a player in another room + if (!hasLineOfSight(this, player)) { + continue + } + + // If the player is invisible, don't follow them + if (player.isInvisible) { + continue + } + + val distance = findDistanceToPlayer(player) + if (distance < closestDistance) { + closestDistance = distance + closestPlayer = player + } + } + + return closestPlayer + } + + /** + * Calculates the distance between the current entity and the specified player. + * + * @param player The player whose distance from the current entity is to be calculated. + * @return The distance between the current entity and the specified player as a Double value. + */ + fun findDistanceToPlayer(player: Player): Double { + return this.location.getDistance(player.location) + } + + /** + * Triggers the current entity to follow the specified player using a movement pulse. + * + * @param player The player that the entity should follow. + */ + fun follow(player: Player) { + pulseManager.run((object : MovementPulse(this, player, Pathfinder.DUMB) { + override fun pulse(): Boolean { + return false + } + }), PulseType.STANDARD) + } + + override fun getIds(): IntArray { + return intArrayOf(DRAYNOR_MANOR_CHAIR_NPC_ID) + } + + override fun shouldPreventStacking(mover: Entity?): Boolean { + return mover is DraynorManorChairNPC + } +} \ No newline at end of file diff --git a/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt new file mode 100644 index 000000000..9478c6656 --- /dev/null +++ b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt @@ -0,0 +1,57 @@ +package content.region.misthalin.draynor.handlers + +import core.game.node.entity.Entity +import core.game.world.map.Location +import core.game.world.map.RegionManager.getLocalNpcs +import core.game.world.map.path.Pathfinder +import core.game.world.map.zone.MapZone +import core.game.world.map.zone.ZoneBorders +import core.game.world.map.zone.ZoneBuilder +import core.plugin.Initializable +import core.plugin.Plugin + +/** + * Represents the interior of Draynor Manor. + * + * @author Broseki + */ +@Initializable +class DraynorManorHouseZone : MapZone("Draynor Manor House", true), Plugin { + + override fun newInstance(arg: Any?): Plugin { + ZoneBuilder.configure(this) + return this + } + + override fun move(e: Entity, loc: Location?, destination: Location): Boolean { + for (n in getLocalNpcs(e, 5)) { + if (n.isInvisible() || n === e) { + continue + } + if (n.shouldPreventStacking(e)) { + val s1 = e.size() + val s2 = n.size() + val x = destination.getX() + val y = destination.getY() + val l = n.getLocation() + if (Pathfinder.isStandingIn(x, y, s1, s1, l.getX(), l.getY(), s2, s2)) { + return false + } + } + } + return true + } + + override fun enter(entity: Entity?): Boolean { + return super.enter(entity) + } + + override fun fireEvent(identifier: String?, vararg args: Any?): Any? { + return null + } + + override fun configure() { + register(ZoneBorders(3097, 3373, 3119, 3364)) + register(ZoneBorders(3090, 3363, 3126, 3354)) + } +} \ No newline at end of file