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...")
} }
anim(player, op) if (clockReady(player, Clocks.SKILLING)) {
forager?.handlePassiveAction() anim(player, op)
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)) {
player.dispatch(ResourceProducedEvent(fish.item.id, fish.item.amount, node)) return restartScript(player)
}
val item = fish.item player.dispatch(ResourceProducedEvent(fish.item.id, fish.item.amount, node))
if (isActive(SkillcapePerks.GREAT_AIM, player) && RandomFunction.roll(20)) {
addItem(player, item.id, item.amount) val item = fish.item
sendMessage(player, colorize("%RYour expert aim catches you a second fish.")) if (isActive(SkillcapePerks.GREAT_AIM, player) && RandomFunction.roll(20)) {
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)
if (!checkRequirements(player, op, node))
return clearScripts(player)
} }
addItemOrDrop(player, item.id, item.amount)
player.incrementAttribute("$STATS_BASE:$STATS_FISH")
rewardXP(player, Skills.FISHING, fish.experience)
setCurrentScriptState(player, 1)
return keepRunning(player) return keepRunning(player)
} }
@@ -118,4 +123,4 @@ class FishingListener : InteractionListener{
} }
return false return false
} }
} }
@@ -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,79 +57,79 @@ 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)) { animateWoodcutting(player)
player.scripts.reset() if (!checkReward(player, resource, tool))
return true return delayClock(player, Clocks.SKILLING, 3)
}
}
animateWoodcutting(player) if (tool.id == Items.INFERNO_ADZE_13661 && RandomFunction.roll(4)) {
if (!checkReward(player, resource, tool)) sendMessage(player, "You chop some logs. The heat of the inferno adze incinerates them.")
return delayScript(player, 3) Projectile.create(
player, null,
if (tool.id == Items.INFERNO_ADZE_13661 && RandomFunction.roll(4)) { 1776,
sendMessage(player, "You chop some logs. The heat of the inferno adze incinerates them.") 35, 30,
Projectile.create( 20, 25
player, null, ).transform(
1776, player,
35, 30, player.location.transform(2, 0, 0),
20, 25 true,
).transform( 25, 25
player, ).send()
player.location.transform(2, 0, 0), delayClock(player, Clocks.SKILLING, 3)
true, return rollDepletion(player, node.asScenery(), resource)
25, 25
).send()
delayScript(player, 3)
return rollDepletion(player, node.asScenery(), resource)
}
val reward = resource.getReward()
val rewardAmount: Int
if (reward > 0) {
rewardAmount = calculateRewardAmount(player, reward) // calculate amount
SkillingPets.checkPetDrop(player, SkillingPets.BEAVER) // roll for pet
//add experience
val experience: Double = calculateExperience(player, resource, rewardAmount)
player.getSkills().addExperience(Skills.WOODCUTTING, experience, true)
//send the message for the resource reward
if (resource == WoodcuttingNode.DRAMEN_TREE) {
player.packetDispatch.sendMessage("You cut a branch from the Dramen tree.")
} else {
player.packetDispatch.sendMessage("You get some " + ItemDefinition.forId(reward).name.lowercase(Locale.getDefault()) + ".")
} }
//give the reward val reward = resource.getReward()
player.inventory.add(Item(reward, rewardAmount)) val rewardAmount: Int
player.dispatch(ResourceProducedEvent(reward, rewardAmount, node, -1)) if (reward > 0) {
var cutLogs = player.getAttribute("$STATS_BASE:$STATS_LOGS", 0) rewardAmount = calculateRewardAmount(player, reward) // calculate amount
player.setAttribute("/save:$STATS_BASE:$STATS_LOGS", ++cutLogs) SkillingPets.checkPetDrop(player, SkillingPets.BEAVER) // roll for pet
//calculate bonus bird nest for mining //add experience
val chance = 282 val experience: Double = calculateExperience(player, resource, rewardAmount)
if (RandomFunction.random(chance) == chance / 2) { player.getSkills().addExperience(Skills.WOODCUTTING, experience, true)
if (isActive(SkillcapePerks.NEST_HUNTER, player)) {
if (!player.inventory.add(BirdNest.getRandomNest(false).nest)) { //send the message for the resource reward
if (resource == WoodcuttingNode.DRAMEN_TREE) {
player.packetDispatch.sendMessage("You cut a branch from the Dramen tree.")
} else {
player.packetDispatch.sendMessage("You get some " + ItemDefinition.forId(reward).name.lowercase(Locale.getDefault()) + ".")
}
//give the reward
player.inventory.add(Item(reward, rewardAmount))
player.dispatch(ResourceProducedEvent(reward, rewardAmount, node, -1))
var cutLogs = player.getAttribute("$STATS_BASE:$STATS_LOGS", 0)
player.setAttribute("/save:$STATS_BASE:$STATS_LOGS", ++cutLogs)
//calculate bonus bird nest for mining
val chance = 282
if (RandomFunction.random(chance) == chance / 2) {
if (isActive(SkillcapePerks.NEST_HUNTER, player)) {
if (!player.inventory.add(BirdNest.getRandomNest(false).nest)) {
BirdNest.drop(player)
}
} else {
BirdNest.drop(player) BirdNest.drop(player)
} }
} else {
BirdNest.drop(player)
} }
} }
}
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)
} }
@@ -244,4 +245,4 @@ class WoodcuttingListener : InteractionListener {
} }
return experience * amount return experience * amount
} }
} }
+23 -1
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
@@ -2389,4 +2411,4 @@ fun modPrayerPoints(player: Player, amount: Double) {
else return else return
} }
private class ContentAPI private class ContentAPI
@@ -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,11 +198,19 @@ class ScriptProcessor(val entity: Entity) {
fun executeScript(script: Script<*>) : Boolean { fun executeScript(script: Script<*>) : Boolean {
currentScript = script currentScript = script
when (script) { try {
is Interaction -> return script.execution.invoke(entity as? Player ?: return true, interactTarget ?: return true, script.state) when (script) {
is UseWithInteraction -> return script.execution.invoke(entity as? Player ?: return true, script.used, script.with, script.state) is Interaction -> return script.execution.invoke(entity as? Player ?: return true, interactTarget ?: return true, script.state)
is QueuedScript -> return script.execution.invoke(script.state) is UseWithInteraction -> 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) 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 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) {
@@ -298,4 +313,4 @@ class ScriptProcessor(val entity: Entity) {
private fun getActiveInteraction() : Script<*>? { private fun getActiveInteraction() : Script<*>? {
return opScript ?: apScript return opScript ?: apScript
} }
} }
@@ -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,14 +26,22 @@ 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){
val e = config as JSONObject try {
val datas = e["loc_data"].toString().split("-") val e = config as JSONObject
val id = e["item_id"].toString().toInt() val datas = e["loc_data"].toString().split("-")
for(d in datas){ val id = e["item_id"].toString().toInt()
val tokens = d.replace("{", "").replace("}", "").split(",".toRegex()).toTypedArray() for(d in datas){
val spawn = GroundSpawn(tokens[4].toInt(), Item(id, tokens[0].toInt()), Location(Integer.valueOf(tokens[1]), Integer.valueOf(tokens[2]), Integer.valueOf(tokens[3]))) if (d.isNullOrEmpty()) continue
spawn.init() val tokens = d.replace("{", "").replace("}", "").split(",".toRegex()).toTypedArray()
count++ 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.") log(this::class.java, Log.FINE, "Initialized $count ground items.")
@@ -118,4 +126,4 @@ class GroundSpawnLoader {
} }
} }
} }
@@ -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()
@@ -139,4 +139,4 @@ class DisconnectionQueue {
} }
return false return false
} }
} }