From 5291b13ff710301f337ff8747e2ea763713b9b6f Mon Sep 17 00:00:00 2001 From: Tooze Date: Wed, 10 Jun 2026 12:41:35 -0400 Subject: [PATCH] penance healer logic --- .../content/minigame/barbassault/BAUtils.kt | 12 ++ .../barbassault/arena/BAArenaListener.kt | 37 +++- .../arena/npcs/PenanceHealerNPC.kt | 169 +++++++++++++++++- 3 files changed, 209 insertions(+), 9 deletions(-) diff --git a/Server/src/main/content/minigame/barbassault/BAUtils.kt b/Server/src/main/content/minigame/barbassault/BAUtils.kt index 3c13c2e50..84e6c64d9 100644 --- a/Server/src/main/content/minigame/barbassault/BAUtils.kt +++ b/Server/src/main/content/minigame/barbassault/BAUtils.kt @@ -3,7 +3,9 @@ package content.minigame.barbassault import content.minigame.barbassault.arena.BarbassaultSession import content.minigame.barbassault.arena.PenanceType import core.api.getTimer +import core.api.hasTimerActive import core.api.registerTimer +import core.api.removeTimer import core.api.spawnTimer import core.game.node.entity.Entity import core.game.node.entity.combat.ImpactHandler @@ -265,3 +267,13 @@ fun applyBAPoison (entity: Entity, source: Entity, severity: Int) { } } +fun cureBAPoison(entity: Entity) { + if (!hasTimerActive(entity)) { + return + } + removeTimer(entity) +} + +fun isBAPoisoned(entity: Entity): Boolean { + return hasTimerActive(entity) +} diff --git a/Server/src/main/content/minigame/barbassault/arena/BAArenaListener.kt b/Server/src/main/content/minigame/barbassault/arena/BAArenaListener.kt index c979dfc6d..a38fca630 100644 --- a/Server/src/main/content/minigame/barbassault/arena/BAArenaListener.kt +++ b/Server/src/main/content/minigame/barbassault/arena/BAArenaListener.kt @@ -145,10 +145,17 @@ class BarbassaultArenaListener : InteractionListener { Pulser.submit(object : Pulse(3, player) { override fun pulse(): Boolean { val restoreAmount = getHealerHealAmount(player) + val curedPoison = isBAPoisoned(player) heal(player, restoreAmount) + cureBAPoison(player) player.settings.updateRunEnergy((-restoreAmount).toDouble()) player.unlock() - sendMessage(player,"You've been healed $restoreAmount hitpoints.") + val message = if (curedPoison) { + "You've been healed $restoreAmount hitpoints and your poison cured." + } else { + "You've been healed $restoreAmount hitpoints." + } + sendMessage(player, message) getBASession(player)?.updateBarbHealerIfaceHPOfPlayer(player) return true } @@ -172,7 +179,8 @@ class BarbassaultArenaListener : InteractionListener { onUseWithPlayer(*All_HEALING_VIALS) { player, _, target -> target as Player //todo find Healing sound - if (target.skills.lifepoints >= target.skills.maximumLifepoints) { + val targetPoisoned = isBAPoisoned(target) + if (target.skills.lifepoints >= target.skills.maximumLifepoints && !targetPoisoned) { sendMessage(player, "The player's hitpoints are full.") return@onUseWithPlayer true } @@ -188,10 +196,11 @@ class BarbassaultArenaListener : InteractionListener { val restoreAmount = minOf(getHealerHealAmount(player), target.skills.maximumLifepoints - target.skills.lifepoints) - sendMessage(target, "You've been healed $restoreAmount hitpoints.") - sendMessage(player, "You healed $restoreAmount hitpoints.") + val curedPoison = isBAPoisoned(target) + sendHealerVialMessages(player, target, restoreAmount, curedPoison) player.animate(Animation(537)) heal(target, restoreAmount) + cureBAPoison(target) target.settings.updateRunEnergy((-restoreAmount).toDouble()) session?.updateBarbHealerIfaceHPOfPlayer(target) session?.eventBus?.emit(HitpointsHealed(player, restoreAmount)) @@ -347,6 +356,24 @@ class BarbassaultArenaListener : InteractionListener { val level = getBALevels(player).heal return healerHealAmounts.getOrElse(level) { healerHealAmounts.last() } } + + private fun sendHealerVialMessages(player: Player, target: Player, restoreAmount: Int, curedPoison: Boolean) { + when { + restoreAmount > 0 && curedPoison -> { + sendMessage(target, "You've been healed $restoreAmount hitpoints and your poison cured.") + sendMessage(player, "You healed $restoreAmount hitpoints and cured their poison.") + } + restoreAmount > 0 -> { + sendMessage(target, "You've been healed $restoreAmount hitpoints.") + sendMessage(player, "You healed $restoreAmount hitpoints.") + } + curedPoison -> { + sendMessage(target, "Your poison has been cured.") + sendMessage(player, "You cured their poison.") + } + } + } + fun fillHealerVial(player: Player){ queueScript(player,1, QueueStrength.STRONG) { if (player.inventory.containsAtLeastOneItem(All_HEALING_VIALS)) { @@ -478,4 +505,4 @@ Blocking off entrance, does NOT STOP runners entering, only slows Announce in game room when All of a monster has been killed " All of the $PENANCE Name have been killed! -* */ \ No newline at end of file +* */ diff --git a/Server/src/main/content/minigame/barbassault/arena/npcs/PenanceHealerNPC.kt b/Server/src/main/content/minigame/barbassault/arena/npcs/PenanceHealerNPC.kt index 18961ca42..c5dcbf61a 100644 --- a/Server/src/main/content/minigame/barbassault/arena/npcs/PenanceHealerNPC.kt +++ b/Server/src/main/content/minigame/barbassault/arena/npcs/PenanceHealerNPC.kt @@ -1,15 +1,24 @@ package content.minigame.barbassault.arena.npcs import content.minigame.barbassault.arena.BarbAssEvent +import content.minigame.barbassault.arena.BarbassaultSession import content.minigame.barbassault.arena.PenanceType +import content.minigame.barbassault.BA_SESSION_KEY +import content.minigame.barbassault.applyBAPoison import content.minigame.barbassault.getBASession +import core.api.forceWalk +import core.api.hasLineOfSight import core.game.node.entity.Entity +import core.game.node.entity.combat.ImpactHandler import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPCBehavior import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills +import core.game.world.map.Location +import core.game.world.map.RegionManager import core.game.world.update.flag.context.Animation import org.rs09.consts.NPCs +import kotlin.math.abs val PENANCE_HEALER_IDS = intArrayOf( //NPCs.PENANCE_HEALER_5043,//tut @@ -33,14 +42,29 @@ val PENANCE_HEALER_DEFINITIONS = listOf( ) class PenanceHealerNPC : NPCBehavior(*PENANCE_HEALER_IDS) { + private enum class HealerMode { TARGET_PLAYER, TARGET_RUNNER, WANDER } + + private companion object { + const val MODE = "ba-healer:mode" + const val TARGET = "ba-healer:target" + const val WANDER_TICKS = "ba-healer:wander-ticks" + const val WANDER_MOVE_TICKS = "ba-healer:wander-move-ticks" + + const val VISION_RANGE = 15 + const val WANDER_AFTER_ACTION_TICKS = 4 + const val WANDER_STEP_TICKS = 5 + const val PLAYER_POISON_DAMAGE = 2 + } + override fun getXpMultiplier(self: NPC, attacker: Entity): Double { return 0.0 } override fun onCreation (self: NPC) { self.setAttribute("barbass-type", PenanceType.HEALER) - self.isWalks = true - self.walkRadius = 24 + self.setAttribute(MODE, HealerMode.TARGET_PLAYER) + self.isWalks = false + self.walkRadius = 0 self.isRespawn = false self.properties.defenceAnimation = Animation(5105) @@ -59,10 +83,147 @@ class PenanceHealerNPC : NPCBehavior(*PENANCE_HEALER_IDS) { } //sendMessage(player,"that's the wrong type of poisoned food to use! Penalty!") override fun tick(self: NPC): Boolean { + val session = self.getAttribute(BA_SESSION_KEY) ?: return false - return true + if (!hasLivingRunners(session)) { + wander(self) + self.shouldPreventStacking(self) + return false + } + + val wanderTicks = self.getAttribute(WANDER_TICKS, 0) + if (wanderTicks > 0) { + self.setAttribute(WANDER_TICKS, wanderTicks - 1) + wander(self) + self.shouldPreventStacking(self) + return false + } + + when (self.getAttribute(MODE, HealerMode.TARGET_PLAYER)) { + HealerMode.TARGET_PLAYER -> tickPlayerTarget(self, session) + HealerMode.TARGET_RUNNER -> tickRunnerTarget(self, session) + HealerMode.WANDER -> wander(self) + } + + self.shouldPreventStacking(self) + return false } + private fun tickPlayerTarget(self: NPC, session: BarbassaultSession) { + val target = currentTarget(self) ?: visiblePlayer(self, session)?.also { setTarget(self, it) } + if (target == null || !isValidPlayerTarget(self, target)) { + clearTarget(self) + return + } + + if (!isCloseEnough(self, target)) { + forceWalk(self, target.location, "DUMB") + return + } + + self.resetWalk() + self.face(target) + target.impactHandler.manualHit(self, PLAYER_POISON_DAMAGE, ImpactHandler.HitsplatType.POISON) + applyBAPoison(target, self, 6) + clearTarget(self) + self.setAttribute(MODE, HealerMode.TARGET_RUNNER) + self.setAttribute(WANDER_TICKS, WANDER_AFTER_ACTION_TICKS) + } + + private fun tickRunnerTarget(self: NPC, session: BarbassaultSession) { + val target = currentTarget(self) ?: visibleRunner(self, session)?.also { setTarget(self, it) } + if (target == null || !isValidRunnerTarget(self, target)) { + clearTarget(self) + return + } + + if (!isCloseEnough(self, target)) { + forceWalk(self, target.location, "DUMB") + return + } + + self.resetWalk() + self.face(target) + target.skills.heal(target.skills.maximumLifepoints) + clearTarget(self) + self.setAttribute(MODE, HealerMode.TARGET_PLAYER) + self.setAttribute(WANDER_TICKS, WANDER_AFTER_ACTION_TICKS) + } + + private fun visiblePlayer(self: NPC, session: BarbassaultSession): Player? { + return session.team.allPlayers() + .filter { isValidPlayerTarget(self, it) } + .minByOrNull { it.location.getDistance(self.location) } + } + + private fun visibleRunner(self: NPC, session: BarbassaultSession): NPC? { + return session.sessionNpcs + .filter { isValidRunnerTarget(self, it) } + .minByOrNull { it.location.getDistance(self.location) } + } + + private fun isValidPlayerTarget(self: NPC, player: Player): Boolean { + return player.skills.lifepoints > 0 && + tileRadius(self.location, player.location) <= VISION_RANGE && + hasLineOfSight(self, player) + } + + private fun isValidRunnerTarget(self: NPC, runner: NPC): Boolean { + return runner.getAttribute("barbass-type", null) == PenanceType.RUNNER && + runner.skills.lifepoints > 0 && + tileRadius(self.location, runner.location) <= VISION_RANGE && + hasLineOfSight(self, runner) + } + + private fun hasLivingRunners(session: BarbassaultSession): Boolean { + return session.sessionNpcs.any { + it.getAttribute("barbass-type", null) == PenanceType.RUNNER && + it.skills.lifepoints > 0 + } + } + + private fun wander(self: NPC) { + val moveTicks = self.getAttribute(WANDER_MOVE_TICKS, 0) + 1 + if (moveTicks < WANDER_STEP_TICKS) { + self.setAttribute(WANDER_MOVE_TICKS, moveTicks) + return + } + + self.setAttribute(WANDER_MOVE_TICKS, 0) + val destination = randomWalkableTile(self.location) ?: return + forceWalk(self, destination, "DUMB") + } + + private fun randomWalkableTile(location: Location): Location? { + val options = listOf( + location.transform(0, 1, 0), + location.transform(1, 0, 0), + location.transform(0, -1, 0), + location.transform(-1, 0, 0), + ).filter { RegionManager.isTeleportPermitted(it) } + + return options.randomOrNull() + } + + private fun isCloseEnough(self: NPC, target: Entity): Boolean { + return self.location == target.location || self.location.isNextTo(target) + } + + private fun tileRadius(a: Location, b: Location): Int { + return maxOf(abs(a.x - b.x), abs(a.y - b.y)) + } + + private fun setTarget(self: NPC, target: Entity) { + self.setAttribute(TARGET, target) + } + + private inline fun currentTarget(self: NPC): T? { + return self.getAttribute(TARGET, null) as? T + } + + private fun clearTarget(self: NPC) { + self.removeAttribute(TARGET) + } override fun onDeathFinished(self: NPC, killer: Entity) { super.onDeathFinished(self, killer) @@ -93,4 +254,4 @@ healer_Idle_5104 healer_hit_5105 healer_Death_5106 Healer_attack_5107? - */ \ No newline at end of file + */