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:
@@ -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...")
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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."))
|
||||
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)) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user