diff --git a/Server/src/main/content/global/handlers/item/ConsumableListener.kt b/Server/src/main/content/global/handlers/item/ConsumableListener.kt index 7a9074f66..f862b16de 100644 --- a/Server/src/main/content/global/handlers/item/ConsumableListener.kt +++ b/Server/src/main/content/global/handlers/item/ConsumableListener.kt @@ -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 } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/skill/gather/fishing/FishingListener.kt b/Server/src/main/content/global/skill/gather/fishing/FishingListener.kt index 01933d9ab..d0b666461 100644 --- a/Server/src/main/content/global/skill/gather/fishing/FishingListener.kt +++ b/Server/src/main/content/global/skill/gather/fishing/FishingListener.kt @@ -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...") } - anim(player, op) - forager?.handlePassiveAction() + 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) - player.dispatch(ResourceProducedEvent(fish.item.id, fish.item.amount, node)) + val fish = op.rollFish(player) ?: return delayClock(player, Clocks.SKILLING, 5) + + if (!hasSpaceFor(player, fish.item) || !op.removeBait(player.inventory)) { + return restartScript(player) + } - val item = fish.item - if (isActive(SkillcapePerks.GREAT_AIM, player) && RandomFunction.roll(20)) { - addItem(player, item.id, item.amount) - sendMessage(player, colorize("%RYour expert aim catches you a second fish.")) + player.dispatch(ResourceProducedEvent(fish.item.id, fish.item.amount, node)) + + val item = fish.item + 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) } @@ -118,4 +123,4 @@ class FishingListener : InteractionListener{ } return false } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt index 987e7dc9e..01e952255 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt @@ -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,79 +57,79 @@ 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 delayClock(player, Clocks.SKILLING, 3) - animateWoodcutting(player) - if (!checkReward(player, resource, tool)) - return delayScript(player, 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.") - Projectile.create( - player, null, - 1776, - 35, 30, - 20, 25 - ).transform( - player, - player.location.transform(2, 0, 0), - true, - 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()) + ".") + if (tool.id == Items.INFERNO_ADZE_13661 && RandomFunction.roll(4)) { + sendMessage(player, "You chop some logs. The heat of the inferno adze incinerates them.") + Projectile.create( + player, null, + 1776, + 35, 30, + 20, 25 + ).transform( + player, + player.location.transform(2, 0, 0), + true, + 25, 25 + ).send() + delayClock(player, Clocks.SKILLING, 3) + return rollDepletion(player, node.asScenery(), resource) } - //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) + val reward = resource.getReward() + val rewardAmount: Int + if (reward > 0) { + rewardAmount = calculateRewardAmount(player, reward) // calculate amount + SkillingPets.checkPetDrop(player, SkillingPets.BEAVER) // roll for pet - //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)) { + //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 + 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) } - } else { - BirdNest.drop(player) } } - } - delayScript(player, 3) - rollDepletion(player, node.asScenery(), resource) - setCurrentScriptState(player, 1) + delayClock(player, Clocks.SKILLING, 3) + rollDepletion(player, node.asScenery(), resource) + if (!checkWoodcuttingRequirements(player, resource, node)) { + return clearScripts(player) + } + } return keepRunning(player) } @@ -244,4 +245,4 @@ class WoodcuttingListener : InteractionListener { } return experience * amount } -} \ No newline at end of file +} diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index 62e707f2b..06cecae20 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -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 @@ -2389,4 +2411,4 @@ fun modPrayerPoints(player: Player, amount: Double) { else return } -private class ContentAPI \ No newline at end of file +private class ContentAPI diff --git a/Server/src/main/core/game/interaction/Clocks.kt b/Server/src/main/core/game/interaction/Clocks.kt index 712c8adba..950ef844a 100644 --- a/Server/src/main/core/game/interaction/Clocks.kt +++ b/Server/src/main/core/game/interaction/Clocks.kt @@ -8,4 +8,5 @@ object Clocks { @JvmStatic val NEXT_DRINK = 4 @JvmStatic val NEXT_ATTACK = 5 @JvmStatic val STUN = 6 -} \ No newline at end of file + @JvmStatic val SKILLING = 7 +} diff --git a/Server/src/main/core/game/interaction/ScriptProcessor.kt b/Server/src/main/core/game/interaction/ScriptProcessor.kt index 089d8dc9e..f1cdfffc0 100644 --- a/Server/src/main/core/game/interaction/ScriptProcessor.kt +++ b/Server/src/main/core/game/interaction/ScriptProcessor.kt @@ -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,11 +198,19 @@ class ScriptProcessor(val entity: Entity) { fun executeScript(script: Script<*>) : Boolean { currentScript = script - 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) + 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) { @@ -298,4 +313,4 @@ class ScriptProcessor(val entity: Entity) { private fun getActiveInteraction() : Script<*>? { return opScript ?: apScript } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/system/config/GroundSpawnLoader.kt b/Server/src/main/core/game/system/config/GroundSpawnLoader.kt index 981b9df3c..baf00ce02 100644 --- a/Server/src/main/core/game/system/config/GroundSpawnLoader.kt +++ b/Server/src/main/core/game/system/config/GroundSpawnLoader.kt @@ -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,14 +26,22 @@ class GroundSpawnLoader { reader = FileReader(ServerConstants.CONFIG_PATH + "ground_spawns.json") var configs = parser.parse(reader) as JSONArray for(config in configs){ - val e = config as JSONObject - val datas = e["loc_data"].toString().split("-") - val id = e["item_id"].toString().toInt() - for(d in datas){ - 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++ + 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.") @@ -118,4 +126,4 @@ class GroundSpawnLoader { } } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/world/repository/DisconnectionQueue.kt b/Server/src/main/core/game/world/repository/DisconnectionQueue.kt index db4e42eaa..8727bd4b7 100644 --- a/Server/src/main/core/game/world/repository/DisconnectionQueue.kt +++ b/Server/src/main/core/game/world/repository/DisconnectionQueue.kt @@ -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() @@ -139,4 +139,4 @@ class DisconnectionQueue { } return false } -} \ No newline at end of file +}