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 {
val consumable = Consumables.getConsumableById(node.id) ?: return stopExecuting(player)
val consumable = Consumables.getConsumableById(node.id) ?: return true
val food = getUsedOption(player) == "eat"
val isIgnoreMainClock = consumable.isIgnoreMainClock
if (food) {
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_EAT] = getWorldTicks() + 2
delayAttack(player, 3)
} 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
delayAttack(player, 3)
}
} else {
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_DRINK] = getWorldTicks() + 3
} 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
}
}
return stopExecuting(player)
return true
}
}
@@ -10,6 +10,7 @@ import core.api.*
import core.game.event.ResourceProducedEvent
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.interaction.Clocks
import core.game.node.Node
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
@@ -47,36 +48,40 @@ class FishingListener : InteractionListener{
return restartScript(player)
if (state == 0) {
sendMessage(player, "You attempt to catch some fish...")
}
if (state == 1) {
if (!checkRequirements(player, op, node))
return clearScripts(player)
forager?.let {
val dest = player.location.transform(player.direction)
Pathfinder.find(it, dest).walk(it)
}
sendMessage(player, "You attempt to catch some fish...")
}
if (clockReady(player, Clocks.SKILLING)) {
anim(player, op)
forager?.handlePassiveAction()
val fish = op.rollFish(player) ?: return delayScript(player, 5)
if (!hasSpaceFor(player, fish.item)) return restartScript(player)
if (!op.removeBait(player.inventory)) return restartScript(player)
val fish = op.rollFish(player) ?: return delayClock(player, Clocks.SKILLING, 5)
if (!hasSpaceFor(player, fish.item) || !op.removeBait(player.inventory)) {
return restartScript(player)
}
player.dispatch(ResourceProducedEvent(fish.item.id, fish.item.amount, node))
val item = fish.item
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."))
}
addItemOrDrop(player, item.id, item.amount)
player.incrementAttribute("$STATS_BASE:$STATS_FISH")
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)
}
@@ -12,6 +12,7 @@ import core.game.container.impl.EquipmentContainer
import core.game.event.ResourceProducedEvent
import core.game.interaction.IntType
import core.game.interaction.InteractionListener
import core.game.interaction.Clocks
import core.game.node.Node
import core.game.node.entity.impl.Projectile
import core.game.node.entity.player.Player
@@ -56,22 +57,19 @@ class WoodcuttingListener : InteractionListener {
val tool = SkillingTool.getHatchet(player)
if (!finishedMoving(player))
return true
return restartScript(player)
if (state == 0) {
if (!checkWoodcuttingRequirements(player, resource, node)) {
return clearScripts(player)
}
sendMessage(player, "You swing your axe at the tree...")
}
if (state == 1) {
if (!checkWoodcuttingRequirements(player, resource, node)) {
player.scripts.reset()
return true
}
}
if (clockReady(player, Clocks.SKILLING)) {
animateWoodcutting(player)
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)) {
sendMessage(player, "You chop some logs. The heat of the inferno adze incinerates them.")
@@ -86,7 +84,7 @@ class WoodcuttingListener : InteractionListener {
true,
25, 25
).send()
delayScript(player, 3)
delayClock(player, Clocks.SKILLING, 3)
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)
setCurrentScriptState(player, 1)
if (!checkWoodcuttingRequirements(player, resource, node)) {
return clearScripts(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)
}
/**
* 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) {
entity.properties.combatPulse.delayNextAttack(3)
entity.clocks[Clocks.NEXT_ATTACK] = getWorldTicks() + ticks
@@ -8,4 +8,5 @@ object Clocks {
@JvmStatic val NEXT_DRINK = 4
@JvmStatic val NEXT_ATTACK = 5
@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.SystemLogger
import java.lang.Integer.max
import java.io.*
class ScriptProcessor(val entity: Entity) {
private var apScript: Script<*>? = null
@@ -182,6 +183,10 @@ class ScriptProcessor(val entity: Entity) {
}
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) {
val finished = executeScript(script)
script.state++
@@ -193,12 +198,20 @@ class ScriptProcessor(val entity: Entity) {
fun executeScript(script: Script<*>) : Boolean {
currentScript = script
try {
when (script) {
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 QueuedScript -> return script.execution.invoke(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
return true
}
@@ -243,6 +256,8 @@ class ScriptProcessor(val entity: Entity) {
}
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()
interactTarget = target
if (script != null) {
@@ -14,7 +14,7 @@ import core.tools.SystemLogger
import core.game.world.GameWorld
import core.game.world.repository.Repository
import core.tools.Log
import java.io.FileReader
import java.io.*
import java.nio.ByteBuffer
class GroundSpawnLoader {
@@ -26,15 +26,23 @@ class GroundSpawnLoader {
reader = FileReader(ServerConstants.CONFIG_PATH + "ground_spawns.json")
var configs = parser.parse(reader) as JSONArray
for(config in configs){
try {
val e = config as JSONObject
val datas = e["loc_data"].toString().split("-")
val id = e["item_id"].toString().toInt()
for(d in datas){
if (d.isNullOrEmpty()) continue
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])))
spawn.init()
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.")
}
@@ -102,7 +102,7 @@ class DisconnectionQueue {
* Clears the queue.
*/
fun clear() {
for (entry in queue.values) {
for (entry in queue.values.toTypedArray()) {
finish(entry, true)
}
queue.clear()