Fixed some error spam about a nullref in animateWoodcutting

Fixed a general script processing oversight that could cause error-throwing scripts to continue executing
Scripts being processed now automatically cancel if the interaction-target-node is no longer active (fixes object could not be replaced spam)
Fixed the stopExecuting() spam
Fixed a CME in the disconnection queue (only affected server shutdown)
Future-proofed the ground spawn parser to work with the format Zaros now outputs
Added in proper SKILLING global clock for use with the script system - addresses some quirks and brings us closer to authenticity
This commit is contained in:
Ceikry
2023-03-21 03:50:27 +00:00
committed by Ryan
parent 48805eea66
commit 7ec901297b
8 changed files with 159 additions and 107 deletions
@@ -19,33 +19,33 @@ class ConsumableListener : InteractionListener {
} }
private fun handleConsumable(player: Player, node: Node) : Boolean { private fun handleConsumable(player: Player, node: Node) : Boolean {
val consumable = Consumables.getConsumableById(node.id) ?: return stopExecuting(player) val consumable = Consumables.getConsumableById(node.id) ?: return true
val food = getUsedOption(player) == "eat" val food = getUsedOption(player) == "eat"
val isIgnoreMainClock = consumable.isIgnoreMainClock val isIgnoreMainClock = consumable.isIgnoreMainClock
if (food) { if (food) {
if (isIgnoreMainClock && player.clocks[Clocks.NEXT_CONSUME] < GameWorld.ticks) { if (isIgnoreMainClock && player.clocks[Clocks.NEXT_CONSUME] < GameWorld.ticks) {
consumable.consumable.consume(node as? Item ?: return stopExecuting(player), player) consumable.consumable.consume(node as? Item ?: return true, player)
player.clocks[Clocks.NEXT_CONSUME] = getWorldTicks() + 2 player.clocks[Clocks.NEXT_CONSUME] = getWorldTicks() + 2
player.clocks[Clocks.NEXT_EAT] = getWorldTicks() + 2 player.clocks[Clocks.NEXT_EAT] = getWorldTicks() + 2
delayAttack(player, 3) delayAttack(player, 3)
} else if (player.clocks[Clocks.NEXT_CONSUME] < getWorldTicks() && player.clocks[Clocks.NEXT_EAT] < getWorldTicks()) { } else if (player.clocks[Clocks.NEXT_CONSUME] < getWorldTicks() && player.clocks[Clocks.NEXT_EAT] < getWorldTicks()) {
consumable.consumable.consume(node as? Item ?: return stopExecuting(player), player) consumable.consumable.consume(node as? Item ?: return true, player)
player.clocks[Clocks.NEXT_EAT] = getWorldTicks() + 2 player.clocks[Clocks.NEXT_EAT] = getWorldTicks() + 2
delayAttack(player, 3) delayAttack(player, 3)
} }
} else { } else {
if (isIgnoreMainClock && player.clocks[Clocks.NEXT_CONSUME] < getWorldTicks()) { if (isIgnoreMainClock && player.clocks[Clocks.NEXT_CONSUME] < getWorldTicks()) {
consumable.consumable.consume(node as? Item ?: return stopExecuting(player), player) consumable.consumable.consume(node as? Item ?: return true, player)
player.clocks[Clocks.NEXT_CONSUME] = getWorldTicks() + 3 player.clocks[Clocks.NEXT_CONSUME] = getWorldTicks() + 3
player.clocks[Clocks.NEXT_DRINK] = getWorldTicks() + 3 player.clocks[Clocks.NEXT_DRINK] = getWorldTicks() + 3
} else if (player.clocks[Clocks.NEXT_CONSUME] < getWorldTicks() && player.clocks[Clocks.NEXT_DRINK] < getWorldTicks()) { } else if (player.clocks[Clocks.NEXT_CONSUME] < getWorldTicks() && player.clocks[Clocks.NEXT_DRINK] < getWorldTicks()) {
consumable.consumable.consume(node as? Item ?: return stopExecuting(player), player) consumable.consumable.consume(node as? Item ?: return true, player)
player.clocks[Clocks.NEXT_DRINK] = getWorldTicks() + 3 player.clocks[Clocks.NEXT_DRINK] = getWorldTicks() + 3
} }
} }
return stopExecuting(player) return true
} }
} }
@@ -10,6 +10,7 @@ import core.api.*
import core.game.event.ResourceProducedEvent import core.game.event.ResourceProducedEvent
import core.game.interaction.IntType import core.game.interaction.IntType
import core.game.interaction.InteractionListener import core.game.interaction.InteractionListener
import core.game.interaction.Clocks
import core.game.node.Node import core.game.node.Node
import core.game.node.entity.npc.NPC import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player import core.game.node.entity.player.Player
@@ -47,36 +48,40 @@ class FishingListener : InteractionListener{
return restartScript(player) return restartScript(player)
if (state == 0) { if (state == 0) {
sendMessage(player, "You attempt to catch some fish...")
}
if (state == 1) {
if (!checkRequirements(player, op, node)) if (!checkRequirements(player, op, node))
return clearScripts(player) return clearScripts(player)
forager?.let { forager?.let {
val dest = player.location.transform(player.direction) val dest = player.location.transform(player.direction)
Pathfinder.find(it, dest).walk(it) Pathfinder.find(it, dest).walk(it)
} }
sendMessage(player, "You attempt to catch some fish...")
} }
if (clockReady(player, Clocks.SKILLING)) {
anim(player, op) anim(player, op)
forager?.handlePassiveAction() forager?.handlePassiveAction()
val fish = op.rollFish(player) ?: return delayScript(player, 5) val fish = op.rollFish(player) ?: return delayClock(player, Clocks.SKILLING, 5)
if (!hasSpaceFor(player, fish.item)) return restartScript(player)
if (!op.removeBait(player.inventory)) return restartScript(player) if (!hasSpaceFor(player, fish.item) || !op.removeBait(player.inventory)) {
return restartScript(player)
}
player.dispatch(ResourceProducedEvent(fish.item.id, fish.item.amount, node)) player.dispatch(ResourceProducedEvent(fish.item.id, fish.item.amount, node))
val item = fish.item val item = fish.item
if (isActive(SkillcapePerks.GREAT_AIM, player) && RandomFunction.roll(20)) { if (isActive(SkillcapePerks.GREAT_AIM, player) && RandomFunction.roll(20)) {
addItem(player, item.id, item.amount) addItemOrDrop(player, item.id, item.amount)
sendMessage(player, colorize("%RYour expert aim catches you a second fish.")) sendMessage(player, colorize("%RYour expert aim catches you a second fish."))
} }
addItemOrDrop(player, item.id, item.amount) addItemOrDrop(player, item.id, item.amount)
player.incrementAttribute("$STATS_BASE:$STATS_FISH") player.incrementAttribute("$STATS_BASE:$STATS_FISH")
rewardXP(player, Skills.FISHING, fish.experience) rewardXP(player, Skills.FISHING, fish.experience)
delayClock(player, Clocks.SKILLING, 5)
setCurrentScriptState(player, 1) if (!checkRequirements(player, op, node))
return clearScripts(player)
}
return keepRunning(player) return keepRunning(player)
} }
@@ -12,6 +12,7 @@ import core.game.container.impl.EquipmentContainer
import core.game.event.ResourceProducedEvent import core.game.event.ResourceProducedEvent
import core.game.interaction.IntType import core.game.interaction.IntType
import core.game.interaction.InteractionListener import core.game.interaction.InteractionListener
import core.game.interaction.Clocks
import core.game.node.Node import core.game.node.Node
import core.game.node.entity.impl.Projectile import core.game.node.entity.impl.Projectile
import core.game.node.entity.player.Player import core.game.node.entity.player.Player
@@ -56,22 +57,19 @@ class WoodcuttingListener : InteractionListener {
val tool = SkillingTool.getHatchet(player) val tool = SkillingTool.getHatchet(player)
if (!finishedMoving(player)) if (!finishedMoving(player))
return true return restartScript(player)
if (state == 0) { if (state == 0) {
if (!checkWoodcuttingRequirements(player, resource, node)) {
return clearScripts(player)
}
sendMessage(player, "You swing your axe at the tree...") sendMessage(player, "You swing your axe at the tree...")
} }
if (state == 1) { if (clockReady(player, Clocks.SKILLING)) {
if (!checkWoodcuttingRequirements(player, resource, node)) {
player.scripts.reset()
return true
}
}
animateWoodcutting(player) animateWoodcutting(player)
if (!checkReward(player, resource, tool)) if (!checkReward(player, resource, tool))
return delayScript(player, 3) return delayClock(player, Clocks.SKILLING, 3)
if (tool.id == Items.INFERNO_ADZE_13661 && RandomFunction.roll(4)) { if (tool.id == Items.INFERNO_ADZE_13661 && RandomFunction.roll(4)) {
sendMessage(player, "You chop some logs. The heat of the inferno adze incinerates them.") sendMessage(player, "You chop some logs. The heat of the inferno adze incinerates them.")
@@ -86,7 +84,7 @@ class WoodcuttingListener : InteractionListener {
true, true,
25, 25 25, 25
).send() ).send()
delayScript(player, 3) delayClock(player, Clocks.SKILLING, 3)
return rollDepletion(player, node.asScenery(), resource) return rollDepletion(player, node.asScenery(), resource)
} }
@@ -126,9 +124,12 @@ class WoodcuttingListener : InteractionListener {
} }
} }
delayScript(player, 3) delayClock(player, Clocks.SKILLING, 3)
rollDepletion(player, node.asScenery(), resource) rollDepletion(player, node.asScenery(), resource)
setCurrentScriptState(player, 1) if (!checkWoodcuttingRequirements(player, resource, node)) {
return clearScripts(player)
}
}
return keepRunning(player) return keepRunning(player)
} }
+22
View File
@@ -2346,6 +2346,28 @@ fun queueScript(entity: Entity, delay: Int = 1, strength: QueueStrength = QueueS
entity.scripts.addToQueue(s, strength) entity.scripts.addToQueue(s, strength)
} }
/**
* Sets the clock to the value of WORLD_TICSK + ticks.
* @param entity the entity whose clock we are updating
* @param clock the clock we are updating. Please use [core.game.interaction.Clocks] for this argument.
* @param ticks the number of ticks to delay by
* @return always returns false so this can be used as a script return value.
**/
fun delayClock(entity: Entity, clock: Int, ticks: Int) : Boolean {
entity.clocks[clock] = getWorldTicks() + ticks
return false
}
/**
* Checks if a clock is ready (have we elapsed any delay put into it)
* @param entity the entity whose clock we are checking
* @param clock the clock we are checking. Please use [core.game.interaction.Clocks] for this argument.
* @return true if we have elapsed the clock's wait
**/
fun clockReady(entity: Entity, clock: Int) : Boolean {
return entity.clocks[clock] <= getWorldTicks()
}
fun delayAttack(entity: Entity, ticks: Int) { fun delayAttack(entity: Entity, ticks: Int) {
entity.properties.combatPulse.delayNextAttack(3) entity.properties.combatPulse.delayNextAttack(3)
entity.clocks[Clocks.NEXT_ATTACK] = getWorldTicks() + ticks entity.clocks[Clocks.NEXT_ATTACK] = getWorldTicks() + ticks
@@ -8,4 +8,5 @@ object Clocks {
@JvmStatic val NEXT_DRINK = 4 @JvmStatic val NEXT_DRINK = 4
@JvmStatic val NEXT_ATTACK = 5 @JvmStatic val NEXT_ATTACK = 5
@JvmStatic val STUN = 6 @JvmStatic val STUN = 6
@JvmStatic val SKILLING = 7
} }
@@ -13,6 +13,7 @@ import core.game.world.map.path.Pathfinder
import core.tools.Log import core.tools.Log
import core.tools.SystemLogger import core.tools.SystemLogger
import java.lang.Integer.max import java.lang.Integer.max
import java.io.*
class ScriptProcessor(val entity: Entity) { class ScriptProcessor(val entity: Entity) {
private var apScript: Script<*>? = null private var apScript: Script<*>? = null
@@ -182,6 +183,10 @@ class ScriptProcessor(val entity: Entity) {
} }
fun processInteractScript(script: Script<*>) { fun processInteractScript(script: Script<*>) {
if (interactTarget == null || !interactTarget!!.isActive) {
log(this::class.java, Log.FINE, "Interact target $interactTarget no longer active, cancelling interaction.")
reset()
}
if (script.nextExecution < GameWorld.ticks) { if (script.nextExecution < GameWorld.ticks) {
val finished = executeScript(script) val finished = executeScript(script)
script.state++ script.state++
@@ -193,12 +198,20 @@ class ScriptProcessor(val entity: Entity) {
fun executeScript(script: Script<*>) : Boolean { fun executeScript(script: Script<*>) : Boolean {
currentScript = script currentScript = script
try {
when (script) { when (script) {
is Interaction -> return script.execution.invoke(entity as? Player ?: return true, interactTarget ?: return true, script.state) is Interaction -> return script.execution.invoke(entity as? Player ?: return true, interactTarget ?: return true, script.state)
is UseWithInteraction -> return script.execution.invoke(entity as? Player ?: return true, script.used, script.with, script.state) is UseWithInteraction -> return script.execution.invoke(entity as? Player ?: return true, script.used, script.with, script.state)
is QueuedScript -> return script.execution.invoke(script.state) is QueuedScript -> return script.execution.invoke(script.state)
is QueuedUseWith -> return script.execution.invoke(entity as? Player ?: return true, script.used, script.with, script.state) is QueuedUseWith -> return script.execution.invoke(entity as? Player ?: return true, script.used, script.with, script.state)
} }
} catch (e: Exception) {
val sw = StringWriter()
val pw = PrintWriter(sw)
e.printStackTrace(pw)
log(this::class.java, Log.ERR, "Error processing ${script::class.java.simpleName} - stopping the script. Exception follows: $sw")
reset()
}
currentScript = null currentScript = null
return true return true
} }
@@ -243,6 +256,8 @@ class ScriptProcessor(val entity: Entity) {
} }
fun setInteractionScript(target: Node, script: Script<*>?) { fun setInteractionScript(target: Node, script: Script<*>?) {
if (apScript != null && script != null && script.execution == apScript!!.execution) return
if (opScript != null && script != null && script.execution == opScript!!.execution) return
reset() reset()
interactTarget = target interactTarget = target
if (script != null) { if (script != null) {
@@ -14,7 +14,7 @@ import core.tools.SystemLogger
import core.game.world.GameWorld import core.game.world.GameWorld
import core.game.world.repository.Repository import core.game.world.repository.Repository
import core.tools.Log import core.tools.Log
import java.io.FileReader import java.io.*
import java.nio.ByteBuffer import java.nio.ByteBuffer
class GroundSpawnLoader { class GroundSpawnLoader {
@@ -26,15 +26,23 @@ class GroundSpawnLoader {
reader = FileReader(ServerConstants.CONFIG_PATH + "ground_spawns.json") reader = FileReader(ServerConstants.CONFIG_PATH + "ground_spawns.json")
var configs = parser.parse(reader) as JSONArray var configs = parser.parse(reader) as JSONArray
for(config in configs){ for(config in configs){
try {
val e = config as JSONObject val e = config as JSONObject
val datas = e["loc_data"].toString().split("-") val datas = e["loc_data"].toString().split("-")
val id = e["item_id"].toString().toInt() val id = e["item_id"].toString().toInt()
for(d in datas){ for(d in datas){
if (d.isNullOrEmpty()) continue
val tokens = d.replace("{", "").replace("}", "").split(",".toRegex()).toTypedArray() val tokens = d.replace("{", "").replace("}", "").split(",".toRegex()).toTypedArray()
val spawn = GroundSpawn(tokens[4].toInt(), Item(id, tokens[0].toInt()), Location(Integer.valueOf(tokens[1]), Integer.valueOf(tokens[2]), Integer.valueOf(tokens[3]))) val spawn = GroundSpawn(tokens[4].toInt(), Item(id, tokens[0].toInt()), Location(Integer.valueOf(tokens[1]), Integer.valueOf(tokens[2]), Integer.valueOf(tokens[3])))
spawn.init() spawn.init()
count++ count++
} }
} catch (e: Exception) {
val sw = StringWriter()
val pw = PrintWriter(sw)
e.printStackTrace(pw)
log(this::class.java, Log.ERR, "Error parsing config entry ${config.toString()}: $sw")
}
} }
log(this::class.java, Log.FINE, "Initialized $count ground items.") log(this::class.java, Log.FINE, "Initialized $count ground items.")
} }
@@ -102,7 +102,7 @@ class DisconnectionQueue {
* Clears the queue. * Clears the queue.
*/ */
fun clear() { fun clear() {
for (entry in queue.values) { for (entry in queue.values.toTypedArray()) {
finish(entry, true) finish(entry, true)
} }
queue.clear() queue.clear()