Talk-to option overrider fix

This commit is contained in:
dam 2026-05-06 14:04:13 +03:00
parent e85f3a6812
commit ae0e3425bd
No known key found for this signature in database
GPG key ID: 4AF4E722399663FB
2 changed files with 256 additions and 2 deletions

View file

@ -162,6 +162,29 @@ object InteractionListeners {
return destinationOverrides["$type:$option"]
}
private fun getOptionHandlerDestination(id: Int, option: String, node: Node): ((Entity, Node) -> Location?)? {
val handlers = ArrayList<OptionHandler>(2)
Option.defaultHandler(node, id, option)?.let { handlers.add(it) }
node.interaction?.options
?.firstOrNull { it != null && it.name.equals(option, ignoreCase = true) }
?.handler
?.takeIf { it !in handlers }
?.let { handlers.add(it) }
if (handlers.isEmpty()) {
return null
}
return { entity, target ->
handlers.firstNotNullOfOrNull { it.getDestination(entity, target) }
}
}
private fun getDestinationOverride(type: Int, id: Int, option: String, node: Node): ((Entity, Node) -> Location?)? {
return getOverride(type, id, option)
?: getOverride(type, node.id)
?: getOverride(type, option.toLowerCase())
?: getOptionHandlerDestination(id, option, node)
}
@JvmStatic
fun run(id: Int, player: Player, node: Node, isEquip: Boolean): Boolean{
player.scripts.removeWeakScripts()
@ -255,7 +278,7 @@ object InteractionListeners {
return true
}
val destOverride = getOverride(type.ordinal, id, option) ?: getOverride(type.ordinal,node.id) ?: getOverride(type.ordinal,option.toLowerCase())
val destOverride = getDestinationOverride(type.ordinal, id, option, node)
if(type != IntType.ITEM && !isInstant(method)) {
if(player.locks.isMovementLocked) return false

View file

@ -1,12 +1,18 @@
package core
import TestUtils
import content.global.handlers.scenery.BankBoothListener
import content.global.handlers.npc.NPCTalkListener
import content.global.skill.gather.GatheringSkillOptionListeners
import content.global.skill.gather.woodcutting.WoodcuttingListener
import content.region.misthalin.varrock.dialogue.GrandExchangeClerk
import content.region.misthalin.varrock.handlers.GrandExchangePlugin
import core.api.log
import core.cache.def.impl.NPCDefinition
import core.game.dialogue.DialogueInterpreter
import core.game.interaction.*
import core.game.node.scenery.Scenery
import core.game.world.map.Direction
import core.game.world.map.Location
import core.game.world.map.RegionManager
import org.junit.jupiter.api.Assertions
@ -24,9 +30,18 @@ import core.plugin.ClassScanner
import core.plugin.Plugin
import core.tools.Log
import org.rs09.consts.NPCs
import org.rs09.consts.Scenery as SceneryIds
class PathfinderTests {
companion object {init {TestUtils.preTestSetup(); GatheringSkillOptionListeners().defineListeners(); WoodcuttingListener().defineListeners() }; val NPC_TEST_LOC = ServerConstants.HOME_LOCATION!!.transform(2, 10, 0)}
companion object {
init {
TestUtils.preTestSetup()
GatheringSkillOptionListeners().defineListeners()
WoodcuttingListener().defineListeners()
BankBoothListener().defineListeners()
}
val NPC_TEST_LOC = ServerConstants.HOME_LOCATION!!.transform(2, 10, 0)
}
@Test fun getOccupiedTilesShouldReturnCorrectSetOfTilesThatAnObjectOccupiesAtAllRotations() {
//clay fireplace - 13609 - sizex: 1, sizey: 2
@ -65,6 +80,222 @@ class PathfinderTests {
)
}
@Test fun dumbPathfinderShouldUseRsmodRouting() {
val start = Location.create(3200, 3200, 0)
val dest = Location.create(3202, 3200, 0)
val blockedMiddle = ClipMaskSupplier { _, x, y ->
if (x == 3201 && y == 3200) 0x100 else 0
}
val path = Pathfinder.DUMB.find(start, 1, dest, 0, 0, 0, -1, 0, false, blockedMiddle)
Assertions.assertTrue(path.isSuccessful)
Assertions.assertTrue(path.points.isNotEmpty())
}
@Test fun projectilePathfinderShouldUseRsmodLineOfSightFlags() {
val start = Location.create(3200, 3200, 0)
val dest = Location.create(3202, 3200, 0)
RegionManager.loadClippingWindow(start, 128)
try {
RegionManager.setRsmodFlag(0, 3201, 3200, true, 0x20000)
val blocked = Pathfinder.PROJECTILE.find(start, 1, dest, 0, 0, 0, -1, 0, false, null)
Assertions.assertFalse(blocked.isSuccessful)
} finally {
RegionManager.setRsmodFlag(0, 3201, 3200, true, 0)
}
}
@Test fun metadataSceneryInteractionShouldTriggerWhenAlreadyAtRsmodApproachTile() {
TestUtils.getMockPlayer("bankBoothApproach").use { p ->
val (booth, approach) = findReachableBankBoothFixture()
p.location = approach
val alreadyAtPath = Pathfinder.find(p, booth)
Assertions.assertTrue(alreadyAtPath.isSuccessful)
Assertions.assertFalse(alreadyAtPath.isMoveNear)
Assertions.assertTrue(InteractionListeners.run(booth.id, IntType.SCENERY, "bank", p, booth))
TestUtils.advanceTicks(10, false)
Assertions.assertTrue(p.bank.isOpen)
}
}
@Test fun metadataSceneryCollectShouldTriggerWhenAlreadyAtRsmodApproachTile() {
TestUtils.getMockPlayer("bankBoothCollectApproach").use { p ->
val (booth, approach) = findReachableBankBoothFixture()
p.location = approach
var collected = false
InteractionListeners.addMetadata(
booth.id,
IntType.SCENERY,
arrayOf("collect"),
InteractionListener.InteractionMetadata({ _, _, _ ->
collected = true
true
}, 1, false)
)
try {
Assertions.assertTrue(InteractionListeners.run(booth.id, IntType.SCENERY, "collect", p, booth))
TestUtils.advanceTicks(10, false)
Assertions.assertTrue(collected)
} finally {
BankBoothListener().defineListeners()
}
}
}
@Test fun directObjectMovementPulseShouldTriggerWhenAlreadyAtRsmodApproachTile() {
TestUtils.getMockPlayer("objectPulseApproach").use { p ->
val tree = RegionManager.getObject(0, 2720, 3475, 1307)
?: throw AssertionError("Expected test tree object.")
val approach = findReachableApproachTile(tree)
var pulsed = false
p.location = approach
GameWorld.Pulser.submit(object : MovementPulse(p, tree) {
override fun pulse(): Boolean {
pulsed = true
return true
}
})
TestUtils.advanceTicks(3, false)
Assertions.assertTrue(pulsed)
}
}
@Test fun entityMovementPulseShouldTriggerWhenDestinationOverrideIsAlreadyReached() {
TestUtils.getMockPlayer("bankerOverrideApproach").use { p ->
val npc = NPC.create(0, NPC_TEST_LOC)
npc.isNeverWalks = true
npc.init()
p.location = ServerConstants.HOME_LOCATION
var pulsed = false
GameWorld.Pulser.submit(object : MovementPulse(p, npc, DestinationFlag.ENTITY, { _, _ -> p.location }) {
override fun pulse(): Boolean {
pulsed = true
return true
}
})
TestUtils.advanceTicks(3, false)
Assertions.assertTrue(pulsed)
}
}
@Test fun interactionListenerShouldUseOptionHandlerDestinationWhenNoListenerDestinationOverride() {
val npc = NPC.create(0, NPC_TEST_LOC)
npc.isNeverWalks = true
npc.init()
var listenerRan = false
var optionHandlerRan = false
val optionName = "listener-custom-destination"
val option = Option(optionName, 4)
val destinationHandler = object : OptionHandler() {
override fun newInstance(arg: Any?): Plugin<Any> {
NPCDefinition.forId(0).handlers["option:$optionName"] = this
return this
}
override fun handle(player: Player?, node: Node?, option: String?): Boolean {
optionHandlerRan = true
return true
}
override fun getDestination(n: Node, node: Node): Location {
return n.location
}
}
destinationHandler.newInstance(null)
option.handler = destinationHandler
npc.interaction.set(option)
InteractionListeners.add(0, IntType.NPC.ordinal, arrayOf(optionName)) { _, _ ->
listenerRan = true
true
}
TestUtils.getMockPlayer("listenerOptionDestination").use { p ->
p.location = ServerConstants.HOME_LOCATION
TestUtils.simulateInteraction(p, npc, 4)
TestUtils.advanceTicks(3, false)
Assertions.assertTrue(listenerRan)
Assertions.assertFalse(optionHandlerRan)
Assertions.assertEquals(ServerConstants.HOME_LOCATION, p.location)
}
}
@Test fun genericTalkToShouldOpenGrandExchangeClerkDialogueFromCounterApproachTile() {
GrandExchangePlugin().newInstance(null)
if (!DialogueInterpreter.contains(6528)) {
GrandExchangeClerk().init()
}
if (InteractionListeners.get("talk-to", IntType.NPC.ordinal) == null) {
NPCTalkListener().defineListeners()
}
val clerk = NPC.create(6528, Location.create(3165, 3491, 0), Direction.NORTH)
clerk.isNeverWalks = true
clerk.init()
try {
TestUtils.getMockPlayer("geClerkTalk").use { p ->
p.location = Location.create(3165, 3492, 0)
TestUtils.simulateInteraction(p, clerk, 0)
TestUtils.advanceTicks(3, false)
Assertions.assertNotNull(p.dialogueInterpreter.dialogue)
Assertions.assertEquals(GrandExchangeClerk::class.java, p.dialogueInterpreter.dialogue.javaClass)
Assertions.assertEquals(Location.create(3165, 3492, 0), p.location)
}
} finally {
clerk.clear()
}
}
private fun findReachableBankBoothFixture(): Pair<Scenery, Location> {
val base = ServerConstants.HOME_LOCATION!!.transform(8, 8, 0)
for (rotation in 0..3) {
val booth = Scenery(SceneryIds.BANK_BOOTH_2213, base, 10, rotation)
runCatching { findReachableApproachTile(booth) }
.getOrNull()
?.let { return booth to it }
}
throw AssertionError("Could not find a reachable synthetic bank booth fixture.")
}
private fun findReachableApproachTile(scenery: Scenery): Location {
for (radius in 1..8) {
for (x in scenery.location.x - radius..scenery.location.x + radius) {
for (y in scenery.location.y - radius..scenery.location.y + radius) {
val start = Location.create(x, y, scenery.location.z)
if (!RegionManager.isTeleportPermitted(start)) {
continue
}
val path = Pathfinder.find(start, scenery)
if (!path.isSuccessful || path.isMoveNear) {
continue
}
val point = path.points.lastOrNull()
val approach = Location.create(point?.x ?: start.x, point?.y ?: start.y, start.z)
val check = Pathfinder.find(approach, scenery)
if (check.isSuccessful && !check.isMoveNear) {
return approach
}
}
}
}
throw AssertionError("Could not find a reachable approach tile for $scenery.")
}
@Test fun movementPulseShouldStopEarlyIfNextToATileOccupiedByTargetObject() {
val start = Location.create(2731, 3481)
val dest = RegionManager.getObject(0, 2720, 3475, 1307)