Assorted bugfixes

Corrected a confidential issue involving random events
Added Lumbridge tree patch to the farming handler
Rewrote seedling handling in new systems
Fixed the incorrect definition for telegrab's spell ID
Fixed an issue that prevented leaving a clan
Close interfaces when applicable when a walk packet is received
Made the penguin state more reliable
Fixed a somewhat rare concurrency bug with the packet processor
Refactored DialogueOption packet to ContinueOption, due to a revelation that it was not particular to dialogue
Fixed a new, unreported issue with the stats interface
This commit is contained in:
Ceikry 2022-12-23 06:35:06 +00:00 committed by Ryan
parent 5011b8648b
commit 9b6a817c9d
11 changed files with 164 additions and 105 deletions

View file

@ -79,7 +79,7 @@ public final class TelekineticGrabSpell extends MagicSpell {
/**
* Represents the spell id.
*/
public static final int SPELL_ID = 65535;
public static final int SPELL_ID = 19;
/**
* Constructs a new {@code TelekineticGrabSpell} {@code Object}.

View file

@ -1,6 +1,8 @@
package api
import core.game.node.item.Item
import java.util.*
import kotlin.collections.ArrayList
fun IntRange.toIntArray(): IntArray {
if (last < first)
@ -53,4 +55,9 @@ fun IntArray.getNext(element: Int) : Int {
fun IntArray.isNextLast(element: Int) : Boolean {
return this.isLast(this.getNext(element))
}
fun <T> LinkedList<T>.tryPop(default: T) : T {
this.peek() ?: return default
return this.pop()
}

View file

@ -1,5 +1,6 @@
package rs09.game.content.ame
import api.Commands
import api.LoginListener
import api.events.EventHook
import api.events.TickEvent
@ -11,10 +12,12 @@ import core.game.world.map.zone.ZoneRestriction
import core.tools.RandomFunction
import rs09.game.Event
import rs09.game.system.SystemLogger
import rs09.game.system.command.Privilege
import rs09.game.world.GameWorld
import rs09.game.world.repository.Repository
import kotlin.random.Random
class RandomEventManager(val player: Player? = null) : LoginListener, EventHook<TickEvent> {
class RandomEventManager(val player: Player? = null) : LoginListener, EventHook<TickEvent>, Commands {
var event: RandomEventNPC? = null
var enabled: Boolean = false
var nextSpawn = 0
@ -71,6 +74,18 @@ class RandomEventManager(val player: Player? = null) : LoginListener, EventHook<
nextSpawn = GameWorld.ticks + RandomFunction.random(MIN_DELAY_TICKS, MAX_DELAY_TICKS)
}
override fun defineCommands() {
define("targeted-ame", Privilege.ADMIN, "targeted-ame username", "Summons a random for the given user") {player, args ->
val username = args[1]
val target = Repository.getPlayerByName(username)
if (target == null)
reject(player, "Unable to find player $username!")
getInstance(target!!)?.fireEvent()
}
}
companion object {
const val AVG_DELAY_TICKS = 6000 // 60 minutes
const val MIN_DELAY_TICKS = AVG_DELAY_TICKS / 2

View file

@ -43,12 +43,14 @@ class PenguinManager{
}
fun rebuildVars() {
if(PenguinHNSEvent.getStoreFile().isEmpty()) {
if(!PenguinHNSEvent.getStoreFile().containsKey("spawned-penguins")) {
penguins = spawner.spawnPenguins(10)
PenguinHNSEvent.getStoreFile()["spawned-penguins"] = penguins.toJSONArray()
tagMapping.clear()
for (p in penguins) {
tagMapping.put(p, JSONArray())
}
updateStoreFile()
} else {
val spawnedOrdinals = (PenguinHNSEvent.getStoreFile()["spawned-penguins"] as JSONArray).map { it.toString().toInt() }
spawner.spawnPenguins(spawnedOrdinals)

View file

@ -33,7 +33,7 @@ class NPCTalkListener : InteractionListener {
on(IntType.NPC,"talk-to","talk","talk to"){player,node ->
val npc = node.asNpc()
if(RandomEvents.randomIDs.contains(node.id)){
if(RandomEventManager.getInstance(player)!!.event == null || RandomEventManager.getInstance(player)!!.event!!.id != node.id){
if(RandomEventManager.getInstance(player)!!.event == null || RandomEventManager.getInstance(player)!!.event!! != node.asNpc()){
player.sendMessage("They aren't interested in talking to you.")
} else {
RandomEventManager.getInstance(player)!!.event!!.talkTo(node.asNpc())

View file

@ -1,50 +1,118 @@
package rs09.game.node.entity.skill.farming
import core.game.interaction.NodeUsageEvent
import core.game.interaction.UseWithHandler
import core.game.node.item.Item
import core.plugin.Initializable
import core.plugin.Plugin
import api.addItem
import api.inInventory
import api.removeItem
import api.sendDialogue
import core.game.node.Node
import core.game.node.entity.player.Player
import org.rs09.consts.Items
import rs09.game.interaction.IntType
import rs09.game.interaction.InteractionListener
import rs09.game.node.entity.state.newsys.states.SeedlingState
@Initializable
class SeedOnPlantPot : UseWithHandler(Items.ACORN_5312,
Items.WILLOW_SEED_5313,Items.MAPLE_SEED_5314,Items.YEW_SEED_5315,Items.MAGIC_SEED_5316,Items.APPLE_TREE_SEED_5283,Items.BANANA_TREE_SEED_5284,Items.ORANGE_TREE_SEED_5285,Items.CURRY_TREE_SEED_5286,Items.PINEAPPLE_SEED_5287,Items.PAPAYA_TREE_SEED_5288,Items.PALM_TREE_SEED_5289) {
override fun newInstance(arg: Any?): Plugin<Any> {
addHandler(Items.PLANT_POT_5354, ITEM_TYPE,this)
return this
class SeedlingListener : InteractionListener {
override fun defineListeners() {
onUseWith(IntType.ITEM, TREE_SEEDS, Items.PLANT_POT_5354, handler = ::addSeedToPot)
onUseWith(IntType.ITEM, TREE_SEEDLINGS, *WATERING_CANS, handler = ::waterSeedling)
}
override fun handle(event: NodeUsageEvent?): Boolean {
val player = event?.player ?: return false
val pot = event.usedItem ?: return false
val seed = event.usedWith.asItem() ?: return false
fun addSeedToPot(player: Player, used: Node, with: Node) : Boolean {
val seed = used.asItem() ?: return false
val pot = with.asItem() ?: return false
if(!player.inventory.contains(Items.GARDENING_TROWEL_5325,1)){
player.dialogueInterpreter.sendDialogue("You need a gardening trowel on you to do this.")
if(!inInventory(player, Items.GARDENING_TROWEL_5325)){
sendDialogue(player, "You need a gardening trowel on you to do this.")
return false
}
val seedling = when(seed.id){
Items.ACORN_5312 -> Items.OAK_SEEDLING_5358
Items.WILLOW_SEED_5313 -> Items.WILLOW_SEEDLING_5359
Items.MAPLE_SEED_5314 -> Items.MAPLE_SEEDLING_5360
Items.YEW_SEED_5315 -> Items.YEW_SEEDLING_5361
Items.MAGIC_SEED_5316 -> Items.MAGIC_SEEDLING_5362
Items.APPLE_TREE_SEED_5283 -> Items.APPLE_SEEDLING_5480
Items.BANANA_TREE_SEED_5284 -> Items.BANANA_SEEDLING_5481
Items.ORANGE_TREE_SEED_5285 -> Items.ORANGE_SEEDLING_5482
Items.CURRY_TREE_SEED_5286 -> Items.CURRY_SEEDLING_5483
Items.PINEAPPLE_SEED_5287 -> Items.PINEAPPLE_SEEDLING_5484
Items.PAPAYA_TREE_SEED_5288 -> Items.PAPAYA_SEEDLING_5485
Items.PALM_TREE_SEED_5289 -> Items.PALM_SEEDLING_5486
else -> return false
val seedling = getSeedling(seed.id)
if (seedling == -1) return false
if (!removeItem(player, seed) || !removeItem(player, pot)) return true
addItem(player, seedling)
return true
}
fun waterSeedling(player: Player, used: Node, with: Node) : Boolean {
val seedling = used.asItem() ?: return false
val can = with.asItem() ?: return false
val nextCan = can.id.getNext()
val wateredSeedling = if (seedling.id > 5400) seedling.id + 8 else seedling.id + 6
if (!removeItem(player, can) || !removeItem(player, seedling)) return false
addItem(player, wateredSeedling)
addItem(player, nextCan)
var state = player.states["seedling"] as SeedlingState?
if (state != null) {
state.addSeedling(wateredSeedling)
return true
}
if(player.inventory.remove(pot) && player.inventory.remove(Item(seed.id,1))){
player.inventory.add(Item(seedling))
}
state = player.registerState("seedling") as SeedlingState?
state?.addSeedling(wateredSeedling)
state?.init()
return true
}
private fun Int.getNext(): Int{
val index = WATERING_CANS.indexOf(this)
if (index == -1) return Items.WATERING_CAN_5331
return if (index != WATERING_CANS.size -1) WATERING_CANS[index + 1] else Items.WATERING_CAN_5331
}
fun getSeedling(id: Int) : Int {
return when (id) {
Items.ACORN_5312 -> Items.OAK_SEEDLING_5358
Items.WILLOW_SEED_5313 -> Items.WILLOW_SEEDLING_5359
Items.MAPLE_SEED_5314 -> Items.MAPLE_SEEDLING_5360
Items.YEW_SEED_5315 -> Items.YEW_SEEDLING_5361
Items.MAGIC_SEED_5316 -> Items.MAGIC_SEEDLING_5362
Items.APPLE_TREE_SEED_5283 -> Items.APPLE_SEEDLING_5480
Items.BANANA_TREE_SEED_5284 -> Items.BANANA_SEEDLING_5481
Items.ORANGE_TREE_SEED_5285 -> Items.ORANGE_SEEDLING_5482
Items.CURRY_TREE_SEED_5286 -> Items.CURRY_SEEDLING_5483
Items.PINEAPPLE_SEED_5287 -> Items.PINEAPPLE_SEEDLING_5484
Items.PAPAYA_TREE_SEED_5288 -> Items.PAPAYA_SEEDLING_5485
Items.PALM_TREE_SEED_5289 -> Items.PALM_SEEDLING_5486
Items.SPIRIT_SEED_5317 -> Items.SPIRIT_SEEDLING_5363
else -> -1
}
}
val TREE_SEEDS = intArrayOf(
Items.ACORN_5312,
Items.WILLOW_SEED_5313,
Items.MAPLE_SEED_5314,
Items.YEW_SEED_5315,
Items.MAGIC_SEED_5316,
Items.APPLE_TREE_SEED_5283,
Items.BANANA_TREE_SEED_5284,
Items.ORANGE_TREE_SEED_5285,
Items.CURRY_TREE_SEED_5286,
Items.PINEAPPLE_SEED_5287,
Items.PAPAYA_TREE_SEED_5288,
Items.PALM_TREE_SEED_5289,
Items.SPIRIT_SEED_5317
)
val TREE_SEEDLINGS = intArrayOf(
Items.OAK_SEEDLING_5358,
Items.WILLOW_SEEDLING_5359,
Items.MAPLE_SEEDLING_5360,
Items.YEW_SEEDLING_5361,
Items.MAGIC_SEEDLING_5362,
Items.APPLE_SEEDLING_5480,
Items.BANANA_SEEDLING_5481,
Items.ORANGE_SEEDLING_5482,
Items.CURRY_SEEDLING_5483,
Items.PINEAPPLE_SEEDLING_5484,
Items.PAPAYA_SEEDLING_5485,
Items.PALM_SEEDLING_5486,
Items.SPIRIT_SEEDLING_5363
)
private val WATERING_CANS = intArrayOf(Items.WATERING_CAN8_5340,Items.WATERING_CAN7_5339,Items.WATERING_CAN6_5338,Items.WATERING_CAN5_5337,Items.WATERING_CAN4_5336,Items.WATERING_CAN3_5335,Items.WATERING_CAN2_5334,Items.WATERING_CAN1_5333)
}

View file

@ -1,54 +0,0 @@
package rs09.game.node.entity.skill.farming
import core.game.interaction.NodeUsageEvent
import core.game.interaction.UseWithHandler
import core.game.node.item.Item
import core.plugin.Initializable
import core.plugin.Plugin
import org.rs09.consts.Items
import rs09.game.node.entity.state.newsys.states.SeedlingState
private val cans = arrayListOf(Items.WATERING_CAN8_5340,Items.WATERING_CAN7_5339,Items.WATERING_CAN6_5338,Items.WATERING_CAN5_5337,Items.WATERING_CAN4_5336,Items.WATERING_CAN3_5335,Items.WATERING_CAN2_5334,Items.WATERING_CAN1_5333)
private val seedlings = arrayListOf(Items.OAK_SEEDLING_5358,Items.WILLOW_SEEDLING_5359,Items.MAPLE_SEEDLING_5360,Items.YEW_SEEDLING_5361,Items.MAGIC_SEEDLING_5362,Items.APPLE_SEEDLING_5480,Items.BANANA_SEEDLING_5481,Items.ORANGE_SEEDLING_5482,Items.CURRY_SEEDLING_5483,Items.PINEAPPLE_SEEDLING_5484,Items.PAPAYA_SEEDLING_5485,Items.PALM_SEEDLING_5486)
@Initializable
class SeedlingWaterer : UseWithHandler(*cans.toIntArray()) {
override fun newInstance(arg: Any?): Plugin<Any> {
for(seed in seedlings) addHandler(seed, ITEM_TYPE,this)
return this
}
override fun handle(event: NodeUsageEvent?): Boolean {
val player = event?.player ?: return false
val seedling = event.used.asItem() ?: return false
val can = event.usedWith.asItem() ?: return false
val nextCan = can.id.getNext()
val wateredSeedling = if(seedling.id > 5400 ) seedling.id + 8 else seedling.id + 6
if(player.inventory.remove(can) && player.inventory.remove(seedling)){
player.inventory.add(Item(wateredSeedling))
player.inventory.add(Item(nextCan))
var state = player.states["seedling"] as SeedlingState?
if(state == null){
state = player.registerState("seedling") as SeedlingState?
state?.addSeedling(wateredSeedling)
state?.init()
} else {
state.addSeedling(wateredSeedling)
}
player.sendMessage("You water the seedling.")
}
return true
}
private fun Int.getNext(): Int{
var index = cans.indexOf(this)
if(index == -1) return Items.WATERING_CAN_5331
if(index != cans.size -1) return cans[index + 1] else return Items.WATERING_CAN_5331
}
}

View file

@ -232,7 +232,7 @@ class UseWithPatchHandler : InteractionListener {
patches.addAll(8550..8557) //allotment wrappers
patches.addAll(7847..7853) //flower patch wrappers
patches.addAll(8150..8156) //herb patch wrappers
patches.addAll(8388..8390) // Tree patches
patches.addAll(8388..8391) // Tree patches
patches.add(19147) //Tree patch
patches.addAll(7962..7965) //fruit trees
patches.addAll(8173..8176) //hops

View file

@ -3,6 +3,7 @@ package rs09.net.packet
import api.events.ButtonClickEvent
import api.getAttribute
import api.sendMessage
import api.tryPop
import core.cache.def.impl.ItemDefinition
import core.cache.def.impl.NPCDefinition
import core.cache.def.impl.SceneryDefinition
@ -17,7 +18,6 @@ import core.game.interaction.NodeUsageEvent
import core.game.interaction.Option
import core.game.interaction.UseWithHandler
import core.game.node.Node
import core.game.node.entity.impl.PulseType
import core.game.node.entity.player.Player
import core.game.node.entity.player.info.Rights
import core.game.node.entity.player.info.login.LoginConfiguration
@ -43,8 +43,8 @@ import discord.Discord
import org.rs09.consts.Components
import proto.management.ClanMessage
import proto.management.JoinClanRequest
import proto.management.LeaveClanRequest
import rs09.ServerConstants
import rs09.game.ge.GrandExchange
import rs09.game.ge.GrandExchange.Companion.getOfferStats
import rs09.game.ge.GrandExchange.Companion.getRecommendedPrice
import rs09.game.ge.GrandExchangeOffer
@ -81,7 +81,7 @@ object PacketProcessor {
val pw = PrintWriter(sw)
var pkt: Packet
while (countThisCycle-- > 0) {
pkt = queue.pop()
pkt = queue.tryPop(Packet.NoProcess())
try {
process(pkt)
} catch (e: Exception) {
@ -127,8 +127,14 @@ object PacketProcessor {
is Packet.PlayerPrefsUpdate -> {/*TODO implement something that cares about this */}
is Packet.Ping -> pkt.player.session.lastPing = System.currentTimeMillis()
is Packet.JoinClan -> {
if (pkt.clanName.isNotEmpty())
sendMessage(pkt.player, "Attempting to join channel....:clan:")
if (pkt.clanName.isEmpty() && pkt.player.communication.currentClan.isNotEmpty()) {
val builder = LeaveClanRequest.newBuilder()
builder.clanName = pkt.player.communication.currentClan
builder.username = pkt.player.name
ManagementEvents.publish(builder.build())
return
}
sendMessage(pkt.player, "Attempting to join channel....:clan:")
val builder = JoinClanRequest.newBuilder()
builder.clanName = pkt.clanName
builder.username = pkt.player.name
@ -228,11 +234,19 @@ object PacketProcessor {
val final = pkt.count - pkt.player.interfaceManager.getPacketCount(0)
pkt.player.interfaceManager.getPacketCount(final)
}
is Packet.DialogueOption -> {
is Packet.ContinueOption -> {
val player = pkt.player
player.debug("[CONTINUE OPT]----------")
player.debug("Iface: ${pkt.iface}")
player.debug("Child: ${pkt.child}")
player.debug("Slot: ${pkt.slot}")
player.debug("------------------------")
if (player.dialogueInterpreter.dialogue == null) {
player.interfaceManager.closeChatbox()
player.dialogueInterpreter.actions.removeFirstOrNull()?.handle(player, pkt.child)
val component = player.interfaceManager.getComponent(pkt.iface) ?: return
if (!InterfaceListeners.run(player, component, pkt.opcode, pkt.child, pkt.slot, -1))
component.plugin?.handle(player, component, pkt.opcode, pkt.child, pkt.slot, -1)
return
}
player.dialogueInterpreter.handle(pkt.iface, pkt.child)
@ -427,7 +441,14 @@ object PacketProcessor {
//there's more data in this packet, we're just not using it
}
if (player.locks.isMovementLocked || !player.interfaceManager.close() || !player.interfaceManager.closeSingleTab() || !player.dialogueInterpreter.close()) {
var canWalk = !player.locks.isMovementLocked
if (canWalk && player.interfaceManager.isOpened && !player.interfaceManager.opened.definition.isWalkable)
canWalk = canWalk && player.interfaceManager.close()
if (canWalk && player.interfaceManager.hasChatbox() && !player.interfaceManager.chatbox.definition.isWalkable)
player.interfaceManager.closeChatbox()
if (!canWalk || !player.dialogueInterpreter.close()) {
player.debug("[WALK ACTION]-- NO HANDLE: PLAYER LOCKED OR INTERFACES SAY NO")
return sendClearMinimap(player)
}

View file

@ -349,12 +349,12 @@ enum class Decoders530(val opcode: Int) {
return Packet.IfAction(player, opcode, -1, iface, button, -1, -1)
}
},
DIALOGUE_OPT(132) {
CONTINUE_OPT(132) {
override fun decode(player: Player, buffer: IoBuffer): Packet {
val ifHash = buffer.intA
val slot = buffer.leShort
val (iface, button) = deHash(ifHash)
return Packet.DialogueOption(player, iface, button)
return Packet.ContinueOption(player, iface, button, slot, 132)
}
},
CLOSE_IFACE(184) {
@ -364,8 +364,8 @@ enum class Decoders530(val opcode: Int) {
},
IF_GROUNDITEM_ACTION(73) {
override fun decode(player: Player, buffer: IoBuffer): Packet {
val child = buffer.short
val iface = buffer.short
val ifHash = buffer.intA
val (iface, child) = deHash(ifHash)
val y = buffer.short
val itemId = buffer.leShortA
val x = buffer.leShortA

View file

@ -17,7 +17,7 @@ sealed class Packet {
data class UseWithScenery(val player: Player, val itemId: Int, val slot: Int, val sceneryId: Int, val x: Int, val y: Int) : Packet()
data class UseWithGroundItem(val player: Player, val usedId: Int, val withId: Int, val iface: Int, val child: Int, val slot: Int, val x: Int, val y: Int) : Packet()
data class IfAction(val player: Player, val opcode: Int, val optIndex: Int, val iface: Int, val child: Int, val slot: Int, val itemId: Int = -1) : Packet()
data class DialogueOption(val player: Player, val iface: Int, val child: Int) : Packet()
data class ContinueOption(val player: Player, val iface: Int, val child: Int, val slot: Int, val opcode: Int) : Packet()
data class CloseIface(val player: Player) : Packet()
data class JoinClan(val player: Player, val clanName: String) : Packet()
data class SetClanRank(val player: Player, val username: String, val rank: Int) : Packet()