Completely rewrote the shop system.

Shop restocking now works properly, with per-item-in-shop granularity as well. (Thanos tool update released that allows defining this)
Shops properly restrict ironmen from buying from player stock/overstocked items
Fully functioning individualized player stock support (need to set a server config variable to enable, disabled by default. Specifically, set personalized_shops = true in the world section of the config.)
The shop pricing formula now scales up/down with the authentic ratios based on stock quantity.
Buying or selling, for example, 1000 of an item, now does the proper calculation for each individual item in the purchase/sale rather than exchanging the full 1000 for the initial price at the time. (For example, in the old shops, feather price at full stock was, say, 6gp. You could buy 1000 feathers at once for 6000gp. Authentically, the pricing for each feather should scale as the stock goes down. Now 1000 feathers at once costs a bit over 7k gp, despite the initial price remaining 6gp.)
General store player stock is now shared between all general stores
This commit is contained in:
Ceikry
2022-04-12 08:46:21 +00:00
committed by Ryan
parent f9aea18497
commit e958b8d255
202 changed files with 1910 additions and 2385 deletions
+49
View File
@@ -0,0 +1,49 @@
package api
import core.game.node.entity.player.Player
import rs09.game.system.command.Command
import rs09.game.system.command.CommandMapping
import rs09.game.system.command.Privilege
import rs09.tools.stringtools.colorize
/**
* An interface for writing content that allows the class to define commands.
*
* Includes methods [reject] and [notify] for notifying the player of a command rejection or some output text respectively.
*
* Includes the method [define] to define individual commands
*
* Commands should ideally be defined in the required [defineCommands] method.
*/
interface Commands : ContentInterface {
/**
* Glorified player.sendMessage with red text coloring. Please use this
* rather than just player.sendMessage for anything that involves informing the player
* of proper usage or invalid syntax. Throws an IllegalStateException and ends command execution immediately.
*/
fun reject(player: Player, vararg message: String){
for(msg in message) {
player.sendMessage(colorize("-->%R$msg"))
}
throw IllegalStateException()
}
/**
* Glorified player.sendMessage with black text coloring and an arrow. Use this when you need to
* notify/inform a player of some information from within the command without ending execution.
*/
fun notify(player: Player, message: String){
player.sendMessage(colorize("-->$message"))
}
/**
* Used to define commands. define("name",(optional) privilege override){ handle }
* @param name the name of the command. Example: ::example would be just "example"
* @param privilege the rights level needed to execute the command. Options are [Privilege.STANDARD], [Privilege.MODERATOR], [Privilege.ADMIN]. Defaults to [Privilege.STANDARD]
*/
fun define(name: String, privilege: Privilege = Privilege.STANDARD, handle: (Player, Array<String>) -> Unit){
CommandMapping.register(Command(name, privilege, handle))
}
fun defineCommands()
}
+16
View File
@@ -1,6 +1,7 @@
package api
import Cutscene
import com.moandjiezana.toml.Toml
import core.cache.def.impl.ItemDefinition
import core.cache.def.impl.SceneryDefinition
import core.game.component.Component
@@ -45,6 +46,7 @@ import rs09.game.content.global.GlobalKillCounter
import rs09.game.interaction.InteractionListeners
import rs09.game.system.SystemLogger
import rs09.game.system.config.ItemConfigParser
import rs09.game.system.config.ServerConfigParser
import rs09.game.world.GameWorld
import rs09.game.world.GameWorld.Pulser
import rs09.game.world.repository.Repository
@@ -714,6 +716,15 @@ fun findLocalNPCs(entity: Entity, ids: IntArray, distance: Int): List<NPC>{
return RegionManager.getLocalNpcs(entity, distance).filter { it.id in ids }.toList()
}
/**
* @param regionId the ID of the region
* @return a [ZoneBorders] encapsulating the entire region indicated by the provided regionId
*/
fun getRegionBorders(regionId: Int) : ZoneBorders
{
return ZoneBorders.forRegion(regionId)
}
/**
* Gets the value of an attribute key from the Entity's attributes store
* @param entity the entity to get the attribute from
@@ -1447,4 +1458,9 @@ fun Player.getCutscene(): Cutscene? {
fun Player.getCutsceneStage(): Int {
return getAttribute(this, Cutscene.ATTRIBUTE_CUTSCENE_STAGE, 0)
}
fun getServerConfig() : Toml
{
return ServerConfigParser.tomlData ?: Toml()
}
@@ -0,0 +1,7 @@
package api
interface ContentInterface {
/**
* A dummy/shell interface that allows us to more easily load content via class scanning
*/
}
@@ -0,0 +1,151 @@
package api
/**
* Used to generate interface settings hashes.
* Deprecates [core.game.container.access.BitregisterAssembler]
* @author Ceikry
*/
class IfaceSettingsBuilder {
/**
* Contains the value which should be sent in access mask packet.
*/
private var value = 0
/**
* Sets right click option settings. If specified option is not allowed, it
* might not appear in the context menu, and if it does, it will throw an error when clicked.
* @param optionId The option index.
*/
fun enableOption(optionId: Int): IfaceSettingsBuilder {
require(!(optionId < 0 || optionId > 9)) { "Option index must be 0-9." }
value = value or (0x1 shl optionId + 1)
return this
}
fun enableOptions(vararg ids: Int): IfaceSettingsBuilder {
for (i in ids.indices) {
enableOption(ids[i])
}
return this
}
fun enableOptions(ids: IntRange): IfaceSettingsBuilder {
for (i in ids.start..ids.endInclusive) {
enableOption(i)
}
return this
}
fun enableAllOptions(): IfaceSettingsBuilder {
for (i in 0..9) {
enableOption(i)
}
return this
}
fun enableOptions(vararg options: String?): IfaceSettingsBuilder {
for (i in options.indices) {
enableOption(i)
}
return this
}
/**
* Sets use on option settings. If nothing is allowed then 'use' option will
* not appear in right click menu.
*/
fun setUseOnSettings(
groundItems: Boolean,
npcs: Boolean,
objects: Boolean,
otherPlayer: Boolean,
selfPlayer: Boolean,
component: Boolean
): IfaceSettingsBuilder {
var useFlag = 0
if (groundItems) {
useFlag = useFlag or 0x1
}
if (npcs) {
useFlag = useFlag or 0x2
}
if (objects) {
useFlag = useFlag or 0x4
}
if (otherPlayer) {
useFlag = useFlag or 0x8
}
if (selfPlayer) {
useFlag = useFlag or 0x10
}
if (component) {
useFlag = useFlag or 0x20
}
value = value or (useFlag shl 11)
return this
}
/**
* Sets interface events depth. For example, we have inventory interface
* which is opened on gameframe interface (548) If depth is 1, then the
* clicks in inventory will also invoke click event handler scripts on
* gameframe interface. Setting depth to 2 also allows dragged items to
* leave the bounds of their container, useful for things such as bank tabs.
* @param depth The depth value.
*/
fun setInterfaceEventsDepth(depth: Int): IfaceSettingsBuilder {
require(!(depth < 0 || depth > 7)) { "depth must be 0-7." }
value = value and (0x7 shl 18).inv()
value = value or (depth shl 18)
return this
}
/**
* Allows items in this interface container to be switched around
* @return this builder
*/
fun enableSlotSwitch(): IfaceSettingsBuilder {
value = value or (1 shl 21)
return this
}
/**
* Allows items in this interface container to have a "Use" option
* @return this builder
*/
fun enableUseOption(): IfaceSettingsBuilder {
value = value or (1 shl 17)
return this
}
fun enableExamine(): IfaceSettingsBuilder {
value = value or (1 shl 9)
return this
}
/**
* Allows this component to have items used on it
* @return this builder
*/
fun enableUseOnSelf(): IfaceSettingsBuilder {
value = value or (1 shl 22)
return this
}
/**
* Allows this component to switch item slots with a slot that contains a null (empty slot)
* @return this builder
*/
fun enableNullSlotSwitch(): IfaceSettingsBuilder {
value = value or (1 shl 23)
return this
}
/**
* Gets the current value.
* @return The value.
*/
fun build(): Int {
return value
}
}
+6 -1
View File
@@ -2,7 +2,12 @@ package api
import core.game.node.entity.player.Player
interface LoginListener {
/**
* An interface for writing content that allows the class to execute some code when a player logs in.
*
* Login listeners are called *before* [PersistPlayer] data is parsed.
*/
interface LoginListener : ContentInterface {
/**
* NOTE: This should NOT reference any non-static class-local variables.
* If you need to access a player's specific instance, use an attribute.
+6 -1
View File
@@ -2,7 +2,12 @@ package api
import core.game.node.entity.player.Player
interface LogoutListener {
/**
* An interface for writing content that allows code to be executed by the class when a player logs out.
*
* Logout listeners are called *before* [PersistPlayer] data is saved.
*/
interface LogoutListener : ContentInterface {
/**
* NOTE: This should NOT reference any non-static class-local variables.
* If you need to access a player's specific instance, use an attribute.
+18
View File
@@ -0,0 +1,18 @@
package api
import core.game.node.entity.Entity
import core.game.world.map.Location
import core.game.world.map.zone.ZoneBorders
import core.game.world.map.zone.ZoneRestriction
/**
* Interface that allows a class to define a map area.
* Optionally-overridable methods include [getRestrictions], [areaEnter], [areaLeave] and [entityStep]
*/
interface MapArea {
fun defineAreaBorders() : Array<ZoneBorders>
fun getRestrictions() : Array<ZoneRestriction> {return arrayOf()}
fun areaEnter(entity: Entity) {}
fun areaLeave(entity: Entity, logout: Boolean) {}
fun entityStep(entity: Entity, location: Location, lastLocation: Location) {}
}
+8 -1
View File
@@ -3,7 +3,14 @@ package api
import core.game.node.entity.player.Player
import org.json.simple.JSONObject
interface PersistPlayer {
/**
* An interface for writing content that allows data to be saved and loaded from player saves.
*
* Parsing is called *after* any [LoginListener] is executed.
*
* Saving is called *after* any [LogoutListener] is executed.
*/
interface PersistPlayer : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
* You need to fetch a player's specific instance of the data and save from that.
@@ -1,6 +0,0 @@
package api
interface PersistWorld {
fun saveWorld()
fun parseWorld()
}
@@ -2,7 +2,10 @@ package api
import rs09.game.system.SystemLogger
interface ShutdownListener {
/**
* An interface for writing content that allows the class to execute code as the server is shutting down
*/
interface ShutdownListener : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
*/
@@ -2,7 +2,10 @@ package api
import rs09.game.system.SystemLogger
interface StartupListener {
/**
* An interface for writing content that allows the class to execute code when the server is started.
*/
interface StartupListener : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
*/
+4 -1
View File
@@ -1,6 +1,9 @@
package api
interface TickListener {
/**
* An interface for writing content that allows the class to be updated each tick.
*/
interface TickListener : ContentInterface {
/**
* NOTE: This should NOT reference nonstatic class-local variables.
* TickListeners are generally for NON-player, WORLD tick events.
@@ -6,17 +6,14 @@ import core.game.world.map.Location
import core.tools.RandomFunction
import org.rs09.consts.Items
import rs09.game.ai.general.GeneralBotCreator
import rs09.game.ai.general.ScriptAPI
import rs09.game.content.global.worldevents.WorldEvents
import rs09.game.content.global.worldevents.shootingstar.ShootingStarEvent
import rs09.game.interaction.InteractionListener.Companion.SCENERY
import rs09.game.content.global.worldevents.shootingstar.ShootingStarPlugin
import rs09.game.interaction.InteractionListener
import rs09.game.interaction.InteractionListeners
import kotlin.concurrent.timer
class ShootingStarBot : Script() {
private var state = State.FULL_IDLE
private var timerCountdown = 0
val star = (WorldEvents.get("shooting-stars") as? ShootingStarEvent)!!.star
val star = ShootingStarPlugin.getStar()
override fun tick() {
bot.fullRestore()
@@ -36,7 +33,7 @@ class ShootingStarBot : Script() {
}
State.MINING -> {
InteractionListeners.run(star.starObject.id, SCENERY, "mine", bot, star.starObject)
InteractionListeners.run(star.starObject.id, InteractionListener.SCENERY, "mine", bot, star.starObject)
}
State.TELEPORT_BACK -> {
@@ -19,7 +19,7 @@ private val LIGHT_ANIM = Animation(7307)
* Handles interactions for beacons
* @author Ceikry
*/
class AFUBeaconListeners : InteractionListener(){
class AFUBeaconListeners : InteractionListener{
override fun defineListeners() {
on(SCENERY,"add-logs","light"){ player, node ->
@@ -16,7 +16,7 @@ import java.util.*
* Handles repairing and climbing of the 3 beacon shortcuts needed to access them
* @author Ceikry
*/
class AFURepairClimbHandler : InteractionListener() {
class AFURepairClimbHandler : InteractionListener {
val repairIDs = intArrayOf(38480,38470,38494)
val climbIDs = intArrayOf(38469,38471,38486,38481,38469)
@@ -11,9 +11,9 @@ import rs09.game.interaction.InterfaceListener
* @author definitely phil who didn't get help from ceikry at all haha :)
* @version 69.0
*/
class BlastFurnaceInterfaceListener : InterfaceListener() {
class BlastFurnaceInterfaceListener : InterfaceListener {
override fun defineListeners() {
override fun defineInterfaceListeners() {
on(Components.BLAST_FURNACE_BAR_STOCK_28){ player, _, _, buttonID, _, _ ->
val bar = BFBars.forId(buttonID) ?: return@on false
@@ -18,7 +18,7 @@ import rs09.game.node.entity.npc.other.BlastFurnaceOre
* That lives in OrdanDialogue.kt
* @author phil lips*/
class BlastFurnaceListeners : InteractionListener() {
class BlastFurnaceListeners : InteractionListener {
val disLoc = getScenery(1941, 4963, 0)
val brokenPotPipe = 9117
@@ -7,9 +7,9 @@ import rs09.game.interaction.InterfaceListener
* Only updates the gauge if people are actually looking at it
* @author phil lips*/
class PhunnyGaugeTempInterfaceListener : InterfaceListener(){
class PhunnyGaugeTempInterfaceListener : InterfaceListener {
override fun defineListeners() {
override fun defineInterfaceListeners() {
onOpen(30) {player, component ->
BlastFurnace.gaugeViewList.add(player)
return@onOpen true
@@ -22,7 +22,7 @@ import kotlin.math.ceil
* Option handler for fishing trawler
* @author Ceikry
*/
class FishingTrawlerInteractionHandler : InteractionListener() {
class FishingTrawlerInteractionHandler : InteractionListener {
val ENTRANCE_PLANK = 2178
val EXIT_PLANK = 2179
val HOLE = 2167
@@ -13,7 +13,7 @@ import rs09.game.interaction.InteractionListener
* @author bushtail
*/
class CraftingGuildListeners : InteractionListener() {
class CraftingGuildListeners : InteractionListener {
private val GUILD_DOOR = Scenery.GUILD_DOOR_2647
private val APRON = Items.BROWN_APRON_1757
private val CAPE = Items.CRAFTING_CAPE_9780
@@ -11,7 +11,7 @@ import rs09.game.interaction.InteractionListener
import rs09.game.node.entity.skill.magic.SpellListener
import rs09.game.node.entity.skill.magic.spellconsts.Modern
class MTAListeners : InteractionListener() {
class MTAListeners : InteractionListener {
override fun defineListeners() {
on(NPCs.MAZE_GUARDIAN_3102,NPC,"talk-to"){player,node ->
player.dialogueInterpreter.open(node.id, node)
@@ -0,0 +1,110 @@
package rs09.game.content.activity.pyramidplunder
import api.EquipmentSlot
import api.openDialogue
import api.sendMessage
import rs09.game.world.GameWorld.Pulser
import rs09.game.content.activity.pyramidplunder.PyramidPlunderMinigame.Companion.GUARDIAN_ROOM
import core.plugin.Initializable
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.update.flag.context.Animation
import core.game.world.update.flag.context.Graphics
import org.rs09.consts.Items
import rs09.game.content.dialogue.DialogueFile
import rs09.game.interaction.InteractionListener
import rs09.tools.END_DIALOGUE
/**
* Adds functionality to the pharoah's scepter
* @author ceik
*/
class PharaohSceptre : InteractionListener {
override fun defineListeners() {
val SCEPTRES = intArrayOf(Items.PHARAOHS_SCEPTRE_9044, Items.PHARAOHS_SCEPTRE_9046, Items.PHARAOHS_SCEPTRE_9048, Items.PHARAOHS_SCEPTRE_9050)
on(SCEPTRES, ITEM, "teleport", "operate"){player, node ->
val sceptre = node.asItem()
if(sceptre.id == SCEPTRES.last())
{
sendMessage(player, "You have used up all the charges on this sceptre.")
return@on true
}
openDialogue(player, SceptreDialog())
return@on true
}
}
class SceptreDialog : DialogueFile() {
fun teleport(location: Location?, player: Player) {
player.lock()
player.visualize(ANIMATION, GRAPHICS)
player.impactHandler.disabledTicks = 4
Pulser.submit(object : Pulse(4, player) {
override fun pulse(): Boolean {
player.unlock()
player.properties.teleportLocation = location
player.animator.reset()
return true
}
})
}
override fun handle(componentID: Int, buttonID: Int) {
if (player!!.isTeleBlocked) {
player!!.sendMessage("A magical force has stopped you from teleporting.")
return
}
when (stage) {
0 -> options("Jalsavrah", "Jaleustrophos", "Jaldraocht", "Nowhere").also { stage++ }
1 -> {
end()
when (buttonID) {
1 -> {
teleport(GUARDIAN_ROOM, player!!)
}
2 -> {
teleport(Location.create(3342, 2827, 0), player!!)
}
3 -> {
teleport(Location.create(3233, 2902, 0), player!!)
}
4 -> return
}
//This sucks but I'm too lazy to fix it.
if (player!!.equipment.containsItem(Item(9044))) {
player!!.equipment.replace(Item(9046), EquipmentSlot.WEAPON.ordinal)
player!!.packetDispatch.sendMessage("<col=7f03ff>Your Pharoah's Sceptre has 2 charges remaining.")
} else if (player!!.equipment.containsItem(Item(9046))) {
player!!.equipment.replace(Item(9048), EquipmentSlot.WEAPON.ordinal)
player!!.packetDispatch.sendMessage("<col=7f03ff>Your Pharoah's Sceptre has 1 charge remaining.")
} else if (player!!.equipment.containsItem(Item(9048))) {
player!!.equipment.replace(Item(9050), EquipmentSlot.WEAPON.ordinal)
player!!.sendMessage("<col=7f03ff>Your Pharoah's Sceptre has used its last charge.")
} else if (player!!.inventory.containsItem(Item(9050))) {
player!!.inventory.remove(Item(9050))
player!!.inventory.add(Item(9048))
player!!.packetDispatch.sendMessage("<col=7f03ff>Your Pharoah's Sceptre has 2 charges remaining.")
} else if (player!!.inventory.containsItem(Item(9048))) {
player!!.inventory.remove(Item(9048))
player!!.inventory.add(Item(9046))
player!!.packetDispatch.sendMessage("<col=7f03ff>Your Pharoah's Sceptre has 1 charge remaining.")
} else if (player!!.inventory.containsItem(Item(9046))) {
player!!.inventory.remove(Item(9046))
player!!.inventory.add(Item(9044))
player!!.sendMessage("<col=7f03ff>Your Pharoah's Sceptre has used its last charge.")
}
}
}
}
}
companion object {
private val GRAPHICS = Graphics(715)
private val ANIMATION = Animation(714)
}
}
@@ -17,11 +17,12 @@ import org.rs09.consts.Scenery
import rs09.game.content.dialogue.DialogueFile
import rs09.game.interaction.InteractionListener
/**
* The "controller" class for pyramid plunder. Handles per-tick updates, logout hooks, and defines interaction listeners for the minigame.
* @author Ceikry
*/
class PyramidPlunderMinigame : InteractionListener(), TickListener, LogoutListener {
class PyramidPlunderMinigame : InteractionListener, TickListener, LogoutListener {
override fun tick() {
val playersToExpel = PlunderUtils.decrementTimeRemaining()
playersToExpel.forEach {player -> PlunderUtils.expel(player, false) }
@@ -1,3 +1,17 @@
package rs09.game.content.activity.vinesweeper
import BlinkinDialogue
import WinkinDialogue
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.DEAD_PLANT_OBJ
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.FARMERS
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.FARMER_CLEAR_RADIUS
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.HOLES
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.NUMBERS
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.RABBIT
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.SEED_LOCS
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.populateSeeds
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.scheduleNPCs
import rs09.game.content.activity.vinesweeper.Vinesweeper.Companion.sendUpdatedPoints
import api.*
import core.game.component.Component
import core.game.node.entity.Entity
@@ -13,164 +27,52 @@ import core.game.node.scenery.SceneryBuilder
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.map.zone.MapZone
import core.game.world.map.zone.ZoneBorders
import core.game.world.map.zone.ZoneBuilder
import core.game.world.update.flag.context.Animation;
import core.game.world.update.flag.context.Graphics;
import core.plugin.Initializable
import core.tools.RandomFunction
import org.rs09.consts.Components
import org.rs09.consts.Items
import rs09.game.content.dialogue.DialogueFile
import rs09.game.interaction.InteractionListener
import rs09.game.interaction.InterfaceListener
import rs09.game.world.GameWorld;
val AVACH_NIMPORTO_LOC = Location.create(1637, 4709)
val PORTAL = 29534
val SIGNS = intArrayOf(29461, 29462, 29463, 29464)
val HOLES = intArrayOf(29476, 29477, 29478)
val NUMBERS = intArrayOf(29447, 29448, 29449, 29450, 29451, 29452, 29453, 29454, 29455)
val DEAD_PLANT_OBJ = 29456
val FLAG_OBJ = 29457
class Vinesweeper : InteractionListener, InterfaceListener, MapArea {
override fun defineAreaBorders(): Array<ZoneBorders> {
return arrayOf(getRegionBorders(6473))
}
val TUTORIAL = 685
val INSTRUCTION_SIGNS = hashMapOf(
29463 to 684,
29464 to 687,
29462 to 688,
29461 to 690
)
val RABBIT = 7125
val FARMERS = intArrayOf(7128, 7129, 7130)
val FARMER_BLINKIN = 7131
val MRS_WINKIN = 7132
val MAX_SEEDS = 300
val FARMER_CLEAR_RADIUS = 3
val VINESWEEPER_BORDERS = ZoneBorders(1600,4672,1663,4735)
fun sendUpdatedPoints(player: Player) {
val points = player.getAttribute("vinesweeper:points", 0);
player.varpManager.get(1195).setVarbit(6, points).send(player)
}
data class SeedDestination(val player: Player, val loc: Location, val alive: Boolean) {
override fun equals(other: Any?): Boolean {
if(other is SeedDestination) {
return loc.equals(other.loc)
} else {
return false
override fun areaEnter(entity: Entity) {
if(entity is Player)
{
openOverlay(entity, Components.RABBIT_OVERLAY_689)
sendUpdatedPoints(entity)
}
}
}
var SEED_LOCS: HashSet<Location> = HashSet()
fun isSeed(loc: Location): Boolean {
val scenery = getScenery(loc)
return scenery != null && SEED_LOCS.contains(scenery.location)
}
fun populateSeeds() {
while(SEED_LOCS.size < MAX_SEEDS) {
val loc = VINESWEEPER_BORDERS.getRandomLoc()
val scenery = getScenery(loc)
if(scenery != null && HOLES.contains(scenery.id)) {
SEED_LOCS.add(loc)
}
}
}
fun isHole(loc: Location): Boolean {
val scenery = getScenery(loc)
return scenery != null && HOLES.contains(scenery.id)
}
fun scheduleNPCs(player: Player, loc: Location, alive: Boolean, rabbit: Boolean) {
val dest = SeedDestination(player, loc, alive)
val ids = if(rabbit) { intArrayOf(RABBIT, *FARMERS) } else { FARMERS }
for(npc in findLocalNPCs(player, ids, 30)) {
if(npc is VinesweeperNPC) {
npc.seedDestinations.add(dest)
npc.resetWalk()
}
}
}
object VinesweeperTeleport {
@JvmStatic
fun teleport(npc: NPC, player: Player) {
npc.animate(Animation(437))
npc.faceTemporary(player, 1)
npc.graphics(Graphics(108))
player.lock()
player.audioManager.send(125)
Projectile.create(npc, player, 109).send()
npc.sendChat("Avach nimporto!")
GameWorld.Pulser.submit(object : Pulse(1) {
var counter = 0
override fun pulse(): Boolean {
when (counter++) {
2 -> {
player.savedData.globalData.essenceTeleporter = npc.id
player.setAttribute("/save:vinesweeper:return-tele:x", npc.location.x)
player.setAttribute("/save:vinesweeper:return-tele:y", npc.location.y)
player.properties.teleportLocation = AVACH_NIMPORTO_LOC
}
3 -> {
player.graphics(Graphics(110))
player.unlock()
return true
}
override fun areaLeave(entity: Entity, logout: Boolean) {
if(entity is Player) {
entity.interfaceManager.closeOverlay()
if(!logout) {
entity.sendMessage("Winkin's Farm thanks you for your visit.")
entity.sendMessage("Leftover ogleroots and flags have been returned to the establishment.")
entity.sendMessage("You have been reimbursed at a rate of 10gp per ogleroot and the flags have been collected.")
val flags = entity.inventory.getAmount(Item(Items.FLAG_12625))
if(flags > 0) {
entity.setAttribute("/save:vinesweeper:stored-flags", flags)
entity.inventory.remove(Item(Items.FLAG_12625, flags))
}
return false
}
})
}
}
class VinesweeperListener : InteractionListener() {
fun dig(player: Player, loc: Location) {
if(isSeed(loc)) {
val oldPoints = player.getAttribute("vinesweeper:points", 0)
player.setAttribute("/save:vinesweeper:points", Math.max(oldPoints-10, 0))
sendUpdatedPoints(player)
player.sendMessage("Oh dear! It looks like you dug up a potato seed by mistake.");
scheduleNPCs(player, loc, false, false)
val scenery = getScenery(loc)
if(scenery != null) {
SceneryBuilder.replace(scenery, scenery.transform(DEAD_PLANT_OBJ))
}
} else {
player.incrementAttribute("/save:vinesweeper:points", 1)
sendUpdatedPoints(player)
var count = 0
for(dx in -1..1) {
for(dy in -1..1) {
if(isSeed(loc.transform(dx, dy, 0))) {
count += 1
}
}
}
val scenery = getScenery(loc)
if(scenery != null) {
SceneryBuilder.replace(scenery, scenery.transform(NUMBERS[count]))
}
if(count == 0) {
for(dx in -1..1) {
for(dy in -1..1) {
val newLoc = loc.transform(dx, dy, 0)
if(isHole(newLoc))
dig(player, newLoc)
}
val roots = entity.inventory.getAmount(Item(Items.OGLEROOT_12624))
if(roots > 0) {
entity.inventory.remove(Item(Items.OGLEROOT_12624, roots))
entity.inventory.add(Item(Items.COINS_995, roots * 10))
}
}
}
}
override fun defineListeners() {
ZoneBuilder.configure(VinesweeperZone())
populateSeeds()
on(PORTAL, SCENERY, "enter") { player, _ ->
val x = player.getAttribute("vinesweeper:return-tele:x", 3052)
@@ -227,7 +129,7 @@ class VinesweeperListener : InteractionListener() {
return@on true
}
on(MRS_WINKIN, NPC, "talk-to") { player, npc ->
openDialogue(player, WinkinDialogue(), npc)
openDialogue(player, WinkinDialogue(), npc)
return@on true
}
on(MRS_WINKIN, NPC, "trade") { player, _ ->
@@ -264,7 +166,7 @@ class VinesweeperListener : InteractionListener() {
}
on(FARMER_BLINKIN, NPC, "talk-to") { player, npc ->
//player.interfaceManager.open(Component(TUTORIAL))
openDialogue(player, BlinkinDialogue(), npc)
openDialogue(player, BlinkinDialogue(), npc)
return@on true
}
on(FARMER_BLINKIN, NPC, "buy-flags") { player, npc ->
@@ -280,240 +182,8 @@ class VinesweeperListener : InteractionListener() {
return@on true
}
}
}
@Initializable
class VinesweeperNPC : AbstractNPC {
fun compareDistance(a: SeedDestination, b: SeedDestination): Int {
val da = a.loc.getDistance(location).toInt()
val db = b.loc.getDistance(location).toInt()
return db - da
}
var seedDestinations: ArrayList<SeedDestination> = ArrayList();
constructor() : super(RABBIT, null, true) {}
private constructor(id: Int, location: Location) : super(id, location) {}
override fun construct(id: Int, location: Location, vararg objects: Any?): AbstractNPC {
return VinesweeperNPC(id, location)
}
init {
walkRadius = 22
}
override fun getIds(): IntArray {
return intArrayOf(RABBIT, *FARMERS)
}
override fun handleTickActions() {
val dest = seedDestinations.find { sd -> sd.loc == location }
if(dest != null) {
for(npc in RegionManager.getRegionPlane(location).npcs) {
if(npc is VinesweeperNPC) {
npc.seedDestinations.remove(dest)
npc.resetWalk()
}
}
val scenery = getScenery(dest.loc)
if(scenery != null) {
if(id == RABBIT) {
val replacement = if(SEED_LOCS.contains(dest.loc)) { DEAD_PLANT_OBJ } else { HOLES[0] }
SceneryBuilder.replace(scenery, scenery.transform(replacement))
scheduleNPCs(dest.player, dest.loc, false, false)
} else {
if(dest.alive) {
handleFarmerFlag(scenery, dest)
} else {
sendChat("Hmm. Looks like there's a plant here.")
lock(3)
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
sendChat("Gracious me! This one's dead")
SceneryBuilder.replace(scenery, scenery.transform(HOLES[0]))
farmerClear(dest)
return true
}
})
}
}
}
seedDestinations.remove(dest)
}
super.handleTickActions()
}
override fun getMovementDestination(): Location? {
if(seedDestinations.size > 0) {
seedDestinations.sortBy { a -> a.loc.getDistance(location).toInt() }
return seedDestinations.first().loc
} else {
return super.getMovementDestination()
}
}
fun handleFarmerFlag(scenery: Scenery, dest: SeedDestination) {
sendChat("Ah, another flag to clear. Let's see what's there.")
lock(3)
animate(Animation(451))
if(SEED_LOCS.contains(dest.loc)) {
val npc = this
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
sendChat("Ah! A seed. Points for everyone near me!")
val level = dest.player.skills.getStaticLevel(Skills.FARMING)
val points = RandomFunction.random(level, 4 * level)
dest.player.incrementAttribute("/save:vinesweeper:points", points)
dest.player.inventory.add(Item(Items.FLAG_12625, 1))
sendUpdatedPoints(dest.player)
for(neighbor in RegionManager.getLocalPlayers(npc)) {
if(neighbor != dest.player) {
neighbor.incrementAttribute("/save:vinesweeper:points", points / 2)
sendUpdatedPoints(neighbor)
}
}
SceneryBuilder.replace(scenery, scenery.transform(HOLES[0]))
farmerClear(dest)
return true
}
})
} else {
SceneryBuilder.replace(scenery, scenery.transform(HOLES[0]))
var i = 0
val lines = arrayOf("Hmm, no seeds planted here, I'm afraid.", "I'll have to keep this 'ere flag. Sorry.")
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
sendChat(lines[i++])
return i >= lines.size
}
})
}
}
fun farmerClear(dest: SeedDestination) {
for(dx in -FARMER_CLEAR_RADIUS..FARMER_CLEAR_RADIUS) {
for(dy in -FARMER_CLEAR_RADIUS..FARMER_CLEAR_RADIUS) {
val toClear = getScenery(dest.loc.transform(dx, dy, 0))
if(toClear != null && intArrayOf(DEAD_PLANT_OBJ, *NUMBERS).contains(toClear.id)) {
SceneryBuilder.replace(toClear, toClear.transform(HOLES[0]))
}
}
}
SEED_LOCS.remove(dest.loc)
populateSeeds()
}
}
class VinesweeperZone : MapZone("Vinesweeper", true) {
override fun enter(e: Entity): Boolean {
if(e is Player) {
e.interfaceManager.openOverlay(Component(689))
sendUpdatedPoints(e)
}
return super.enter(e)
}
override fun leave(e: Entity, logout: Boolean): Boolean {
if(e is Player) {
e.interfaceManager.closeOverlay()
if(!logout) {
e.sendMessage("Winkin's Farm thanks you for your visit.")
e.sendMessage("Leftover ogleroots and flags have been returned to the establishment.")
e.sendMessage("You have been reimbursed at a rate of 10gp per ogleroot and the flags have been collected.")
val flags = e.inventory.getAmount(Item(Items.FLAG_12625))
if(flags > 0) {
e.setAttribute("/save:vinesweeper:stored-flags", flags)
e.inventory.remove(Item(Items.FLAG_12625, flags))
}
val roots = e.inventory.getAmount(Item(Items.OGLEROOT_12624))
if(roots > 0) {
e.inventory.remove(Item(Items.OGLEROOT_12624, roots))
e.inventory.add(Item(Items.COINS_995, roots * 10))
}
}
}
return super.leave(e, logout)
}
override fun configure() {
super.registerRegion(6473)
}
}
class VinesweeperRewards : InterfaceListener() {
val IFACE = 686
val TRADE_FOR_XP_BUTTON = 53
val XP_CONFIRM = 72
val XP_DENY = 73
enum class Opcode(val value: Int) {
VALUE(155),
BUY1(196),
BUY5(124),
BUY10(199),
BUYX(234),
}
data class Reward(val itemID: Int, val points: Int) {}
val REWARDS = hashMapOf(
18 to Reward(Items.TOMATO_SEED_5322, 10),
19 to Reward(Items.SWEETCORN_SEED_5320, 150),
20 to Reward(Items.STRAWBERRY_SEED_5323, 165),
21 to Reward(Items.WATERMELON_SEED_5321, 680),
22 to Reward(Items.GUAM_SEED_5291, 10),
23 to Reward(Items.MARRENTILL_SEED_5292, 10),
24 to Reward(Items.RANARR_SEED_5295, 4000),
25 to Reward(Items.KWUARM_SEED_5299, 1000),
26 to Reward(Items.TARROMIN_SEED_5293, 10),
27 to Reward(Items.NASTURTIUM_SEED_5098, 10),
28 to Reward(Items.WOAD_SEED_5099, 30),
29 to Reward(Items.LIMPWURT_SEED_5100, 70),
30 to Reward(Items.ASGARNIAN_SEED_5308, 5),
31 to Reward(Items.KRANDORIAN_SEED_5310, 20),
32 to Reward(Items.REDBERRY_SEED_5101, 5),
33 to Reward(Items.CADAVABERRY_SEED_5102, 5),
34 to Reward(Items.DWELLBERRY_SEED_5103, 5),
35 to Reward(Items.JANGERBERRY_SEED_5104, 10),
36 to Reward(Items.WHITEBERRY_SEED_5105, 25),
37 to Reward(Items.POISON_IVY_SEED_5106, 30),
38 to Reward(Items.ACORN_5312, 100),
39 to Reward(Items.WILLOW_SEED_5313, 1800),
40 to Reward(Items.MAPLE_SEED_5314, 12000),
41 to Reward(Items.PINEAPPLE_SEED_5287, 10000),
42 to Reward(Items.YEW_SEED_5315, 29000),
43 to Reward(Items.PALM_TREE_SEED_5289, 35000),
44 to Reward(Items.SPIRIT_SEED_5317, 55000),
45 to Reward(Items.COMPOST_POTION4_6470, 5000),
46 to Reward(Items.FLAG_12625, 50),
)
fun buy(player: Player, buttonID: Int, amount: Int) {
val reward = REWARDS[buttonID] ?: return
val cost = amount * reward.points
val points = player.getAttribute("vinesweeper:points", 0)
if(cost in 1 until points) {
val item = Item(reward.itemID, amount)
if(!player.inventory.add(item)) {
GroundItemManager.create(item, player)
}
player.incrementAttribute("/save:vinesweeper:points", -cost)
sendUpdatedPoints(player)
} else {
// TODO (crash): authenticity
player.sendMessage("You don't have enough points for that.")
}
}
override fun defineListeners() {
onOpen(IFACE) { _, _ ->
/*for((buttonID, reward) in REWARDS) {
sendItemOnInterface(player, IFACE, buttonID, reward.itemID, 5)
}*/
//player.packetDispatch.sendRunScript(2003, "")
return@onOpen true
}
override fun defineInterfaceListeners() {
on(IFACE) { player, _, opcode, buttonID, _, _ ->
when(opcode) {
Opcode.VALUE.value -> {
@@ -566,6 +236,338 @@ class VinesweeperRewards : InterfaceListener() {
return@on true
}
}
data class SeedDestination(val player: Player, val loc: Location, val alive: Boolean) {
override fun equals(other: Any?): Boolean {
return if(other is SeedDestination) {
loc == other.loc
} else {
false
}
}
override fun hashCode(): Int {
return loc.hashCode()
}
}
companion object
{
val AVACH_NIMPORTO_LOC = Location.create(1637, 4709)
val PORTAL = 29534
val SIGNS = intArrayOf(29461, 29462, 29463, 29464)
val HOLES = intArrayOf(29476, 29477, 29478)
val NUMBERS = intArrayOf(29447, 29448, 29449, 29450, 29451, 29452, 29453, 29454, 29455)
val DEAD_PLANT_OBJ = 29456
val FLAG_OBJ = 29457
val IFACE = 686
val TRADE_FOR_XP_BUTTON = 53
val XP_CONFIRM = 72
val XP_DENY = 73
enum class Opcode(val value: Int) {
VALUE(155),
BUY1(196),
BUY5(124),
BUY10(199),
BUYX(234),
}
data class Reward(val itemID: Int, val points: Int) {}
val REWARDS = hashMapOf(
18 to Reward(Items.TOMATO_SEED_5322, 10),
19 to Reward(Items.SWEETCORN_SEED_5320, 150),
20 to Reward(Items.STRAWBERRY_SEED_5323, 165),
21 to Reward(Items.WATERMELON_SEED_5321, 680),
22 to Reward(Items.GUAM_SEED_5291, 10),
23 to Reward(Items.MARRENTILL_SEED_5292, 10),
24 to Reward(Items.RANARR_SEED_5295, 4000),
25 to Reward(Items.KWUARM_SEED_5299, 1000),
26 to Reward(Items.TARROMIN_SEED_5293, 10),
27 to Reward(Items.NASTURTIUM_SEED_5098, 10),
28 to Reward(Items.WOAD_SEED_5099, 30),
29 to Reward(Items.LIMPWURT_SEED_5100, 70),
30 to Reward(Items.ASGARNIAN_SEED_5308, 5),
31 to Reward(Items.KRANDORIAN_SEED_5310, 20),
32 to Reward(Items.REDBERRY_SEED_5101, 5),
33 to Reward(Items.CADAVABERRY_SEED_5102, 5),
34 to Reward(Items.DWELLBERRY_SEED_5103, 5),
35 to Reward(Items.JANGERBERRY_SEED_5104, 10),
36 to Reward(Items.WHITEBERRY_SEED_5105, 25),
37 to Reward(Items.POISON_IVY_SEED_5106, 30),
38 to Reward(Items.ACORN_5312, 100),
39 to Reward(Items.WILLOW_SEED_5313, 1800),
40 to Reward(Items.MAPLE_SEED_5314, 12000),
41 to Reward(Items.PINEAPPLE_SEED_5287, 10000),
42 to Reward(Items.YEW_SEED_5315, 29000),
43 to Reward(Items.PALM_TREE_SEED_5289, 35000),
44 to Reward(Items.SPIRIT_SEED_5317, 55000),
45 to Reward(Items.COMPOST_POTION4_6470, 5000),
46 to Reward(Items.FLAG_12625, 50),
)
fun buy(player: Player, buttonID: Int, amount: Int) {
val reward = REWARDS[buttonID] ?: return
val cost = amount * reward.points
val points = player.getAttribute("vinesweeper:points", 0)
if(cost in 1 until points) {
val item = Item(reward.itemID, amount)
if(!player.inventory.add(item)) {
GroundItemManager.create(item, player)
}
player.incrementAttribute("/save:vinesweeper:points", -cost)
sendUpdatedPoints(player)
} else {
// TODO (crash): authenticity
player.sendMessage("You don't have enough points for that.")
}
}
val TUTORIAL = 685
val INSTRUCTION_SIGNS = hashMapOf(
29463 to 684,
29464 to 687,
29462 to 688,
29461 to 690
)
val RABBIT = 7125
val FARMERS = intArrayOf(7128, 7129, 7130)
val FARMER_BLINKIN = 7131
val MRS_WINKIN = 7132
val MAX_SEEDS = 300
val FARMER_CLEAR_RADIUS = 3
val VINESWEEPER_BORDERS = ZoneBorders(1600,4672,1663,4735)
fun sendUpdatedPoints(player: Player) {
val points = player.getAttribute("vinesweeper:points", 0);
player.varpManager.get(1195).setVarbit(6, points).send(player)
}
var SEED_LOCS: HashSet<Location> = HashSet()
fun isSeed(loc: Location): Boolean {
val scenery = getScenery(loc)
return scenery != null && SEED_LOCS.contains(scenery.location)
}
fun populateSeeds() {
while(SEED_LOCS.size < MAX_SEEDS) {
val loc = VINESWEEPER_BORDERS.getRandomLoc()
val scenery = getScenery(loc)
if(scenery != null && HOLES.contains(scenery.id)) {
SEED_LOCS.add(loc)
}
}
}
fun dig(player: Player, loc: Location) {
if(isSeed(loc)) {
val oldPoints = player.getAttribute("vinesweeper:points", 0)
player.setAttribute("/save:vinesweeper:points", Math.max(oldPoints-10, 0))
sendUpdatedPoints(player)
player.sendMessage("Oh dear! It looks like you dug up a potato seed by mistake.");
scheduleNPCs(player, loc, false, false)
val scenery = getScenery(loc)
if(scenery != null) {
SceneryBuilder.replace(scenery, scenery.transform(DEAD_PLANT_OBJ))
}
} else {
player.incrementAttribute("/save:vinesweeper:points", 1)
sendUpdatedPoints(player)
var count = 0
for(dx in -1..1) {
for(dy in -1..1) {
if(isSeed(loc.transform(dx, dy, 0))) {
count += 1
}
}
}
val scenery = getScenery(loc)
if(scenery != null) {
SceneryBuilder.replace(scenery, scenery.transform(NUMBERS[count]))
}
if(count == 0) {
for(dx in -1..1) {
for(dy in -1..1) {
val newLoc = loc.transform(dx, dy, 0)
if(isHole(newLoc))
dig(player, newLoc)
}
}
}
}
}
fun isHole(loc: Location): Boolean {
val scenery = getScenery(loc)
return scenery != null && HOLES.contains(scenery.id)
}
fun scheduleNPCs(player: Player, loc: Location, alive: Boolean, rabbit: Boolean) {
val dest = SeedDestination(player, loc, alive)
val ids = if(rabbit) { intArrayOf(RABBIT, *FARMERS) } else { FARMERS }
for(npc in findLocalNPCs(player, ids, 30)) {
if(npc is VinesweeperNPC) {
npc.seedDestinations.add(dest)
npc.resetWalk()
}
}
}
object VinesweeperTeleport {
@JvmStatic
fun teleport(npc: NPC, player: Player) {
npc.animate(Animation(437))
npc.faceTemporary(player, 1)
npc.graphics(Graphics(108))
player.lock()
player.audioManager.send(125)
Projectile.create(npc, player, 109).send()
npc.sendChat("Avach nimporto!")
GameWorld.Pulser.submit(object : Pulse(1) {
var counter = 0
override fun pulse(): Boolean {
when (counter++) {
2 -> {
player.savedData.globalData.essenceTeleporter = npc.id
player.setAttribute("/save:vinesweeper:return-tele:x", npc.location.x)
player.setAttribute("/save:vinesweeper:return-tele:y", npc.location.y)
player.properties.teleportLocation = AVACH_NIMPORTO_LOC
}
3 -> {
player.graphics(Graphics(110))
player.unlock()
return true
}
}
return false
}
})
}
}
}
}
@Initializable
class VinesweeperNPC : AbstractNPC {
var seedDestinations: ArrayList<Vinesweeper.SeedDestination> = ArrayList();
constructor() : super(RABBIT, null, true) {}
private constructor(id: Int, location: Location) : super(id, location) {}
override fun construct(id: Int, location: Location, vararg objects: Any?): AbstractNPC {
return VinesweeperNPC(id, location)
}
init {
walkRadius = 22
}
override fun getIds(): IntArray {
return intArrayOf(RABBIT, *FARMERS)
}
override fun handleTickActions() {
val dest = seedDestinations.find { sd -> sd.loc == location }
if(dest != null) {
for(npc in RegionManager.getRegionPlane(location).npcs) {
if(npc is VinesweeperNPC) {
npc.seedDestinations.remove(dest)
npc.resetWalk()
}
}
val scenery = getScenery(dest.loc)
if(scenery != null) {
if(id == RABBIT) {
val replacement = if(SEED_LOCS.contains(dest.loc)) { DEAD_PLANT_OBJ } else { HOLES[0] }
SceneryBuilder.replace(scenery, scenery.transform(replacement))
scheduleNPCs(dest.player, dest.loc, alive = false, rabbit = false)
} else {
if(dest.alive) {
handleFarmerFlag(scenery, dest)
} else {
sendChat("Hmm. Looks like there's a plant here.")
lock(3)
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
sendChat("Gracious me! This one's dead")
SceneryBuilder.replace(scenery, scenery.transform(HOLES[0]))
farmerClear(dest)
return true
}
})
}
}
}
seedDestinations.remove(dest)
}
super.handleTickActions()
}
override fun getMovementDestination(): Location? {
if(seedDestinations.size > 0) {
seedDestinations.sortBy { a -> a.loc.getDistance(location).toInt() }
return seedDestinations.first().loc
} else {
return super.getMovementDestination()
}
}
fun handleFarmerFlag(scenery: Scenery, dest: Vinesweeper.SeedDestination) {
sendChat("Ah, another flag to clear. Let's see what's there.")
lock(3)
animate(Animation(451))
if(SEED_LOCS.contains(dest.loc)) {
val npc = this
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
sendChat("Ah! A seed. Points for everyone near me!")
val level = dest.player.skills.getStaticLevel(Skills.FARMING)
val points = RandomFunction.random(level, 4 * level)
dest.player.incrementAttribute("/save:vinesweeper:points", points)
dest.player.inventory.add(Item(Items.FLAG_12625, 1))
sendUpdatedPoints(dest.player)
for(neighbor in RegionManager.getLocalPlayers(npc)) {
if(neighbor != dest.player) {
neighbor.incrementAttribute("/save:vinesweeper:points", points / 2)
sendUpdatedPoints(neighbor)
}
}
SceneryBuilder.replace(scenery, scenery.transform(HOLES[0]))
farmerClear(dest)
return true
}
})
} else {
SceneryBuilder.replace(scenery, scenery.transform(HOLES[0]))
var i = 0
val lines = arrayOf("Hmm, no seeds planted here, I'm afraid.", "I'll have to keep this 'ere flag. Sorry.")
GameWorld.Pulser.submit(object : Pulse(3) {
override fun pulse(): Boolean {
sendChat(lines[i++])
return i >= lines.size
}
})
}
}
fun farmerClear(dest: Vinesweeper.SeedDestination) {
for(dx in -FARMER_CLEAR_RADIUS..FARMER_CLEAR_RADIUS) {
for(dy in -FARMER_CLEAR_RADIUS..FARMER_CLEAR_RADIUS) {
val toClear = getScenery(dest.loc.transform(dx, dy, 0))
if(toClear != null && intArrayOf(DEAD_PLANT_OBJ, *NUMBERS).contains(toClear.id)) {
SceneryBuilder.replace(toClear, toClear.transform(HOLES[0]))
}
}
}
SEED_LOCS.remove(dest.loc)
populateSeeds()
}
}
@@ -7,7 +7,7 @@ import core.game.node.item.Item
import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
class WGuildListeners : InteractionListener() {
class WGuildListeners : InteractionListener {
override fun defineListeners() {
onEquip(Items.DEFENSIVE_SHIELD_8856){player, node ->
if (node is Item) {
@@ -6,7 +6,7 @@ import org.rs09.consts.Items
import rs09.game.content.ame.RandomEventManager
import rs09.game.interaction.InterfaceListener
class CerterEventInterface : InterfaceListener() {
class CerterEventInterface : InterfaceListener {
val CERTER_INTERFACE = 184
val OPTION_A_CHILD = 1
val OPTION_B_CHILD = 2
@@ -31,7 +31,7 @@ class CerterEventInterface : InterfaceListener() {
Items.NULL_6198 to "A necklace."
)
val falseOptions = arrayOf("An axe.", "An arrow.", "A pair of boots.", "A pair of gloves.", "A staff.", "A bow.", "A feather.", "The disenfrachaised youth of 1940's Columbia.")
override fun defineListeners() {
override fun defineInterfaceListeners() {
on(CERTER_INTERFACE) { player, _, _, buttonID, _, _ ->
val answer = buttonID - 7
val correctAnswer = player.getAttribute("certer:correctIndex", 0)
@@ -5,7 +5,7 @@ import core.game.system.task.Pulse
import org.rs09.consts.NPCs
import rs09.game.interaction.InteractionListener
class DrillDemonListeners : InteractionListener() {
class DrillDemonListeners : InteractionListener {
val MATS = intArrayOf(10076,10077,10078,10079)
override fun defineListeners() {
@@ -5,7 +5,7 @@ import org.rs09.consts.Items
import rs09.game.content.ame.RandomEventManager
import rs09.game.interaction.InterfaceListener
class SandwichLadyInterface : InterfaceListener(){
class SandwichLadyInterface : InterfaceListener{
val SANDWICH_INTERFACE = 297
val baguette = Items.BAGUETTE_6961
@@ -16,7 +16,7 @@ class SandwichLadyInterface : InterfaceListener(){
val kebab = Items.KEBAB_1971
val chocobar = Items.CHOCOLATE_BAR_1973
override fun defineListeners() {
override fun defineInterfaceListeners() {
on(SANDWICH_INTERFACE){player, _, _, buttonID, _, _ ->
val item =
when(buttonID) {
@@ -5,11 +5,11 @@ import org.rs09.consts.Components
import org.rs09.consts.NPCs
import rs09.game.interaction.InterfaceListener
class SEPatternInterface : InterfaceListener() {
class SEPatternInterface : InterfaceListener {
val COMPONENT = Components.PATTERN_NEXT_103
override fun defineListeners() {
override fun defineInterfaceListeners() {
on(COMPONENT){player, component, opcode, buttonID, slot, itemID ->
val index = buttonID - 10
@@ -9,7 +9,7 @@ import org.rs09.consts.NPCs
import rs09.game.interaction.InteractionListener
import rs09.game.interaction.inter.ExperienceInterface
class SupriseExamListeners : InteractionListener() {
class SupriseExamListeners : InteractionListener {
val MORDAUT = NPCs.MR_MORDAUT_6117
val BOOK_OF_KNOWLEDGE = Items.BOOK_OF_KNOWLEDGE_11640
override fun defineListeners() {
@@ -5,6 +5,7 @@ import core.game.content.dialogue.FacialExpression
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.plugin.Initializable
import rs09.game.content.global.shops.Shops
import rs09.game.system.config.ShopParser
/**
@@ -116,8 +117,8 @@ class GabootyDialogue(player: Player? = null) : DialoguePlugin(player){
"you know, a little more of this, a little less of that, it all",
"adds up and makes for an interesting tipple!").also { stage = 23 }
90 -> end().also { ShopParser.Companion.openUid(player,226) }
91 -> end().also { ShopParser.Companion.openUid(player,227) }
90 -> end().also { Shops.openId(player,226) }
91 -> end().also { Shops.openId(player,227) }
99 -> end()
}
@@ -20,7 +20,7 @@ import rs09.game.system.config.ItemConfigParser
* @author Ceikry
* @author Woah
*/
class EquipHandler : InteractionListener() {
class EquipHandler : InteractionListener {
override fun defineListeners() {
@@ -0,0 +1,393 @@
package rs09.game.content.global.shops
import api.*
import core.game.component.Component
import core.game.container.Container
import core.game.container.ContainerEvent
import core.game.container.ContainerListener
import core.game.container.ContainerType
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.net.packet.PacketRepository
import core.net.packet.context.ContainerContext
import core.net.packet.out.ContainerPacket
import org.rs09.consts.Components
import org.rs09.consts.Items
import rs09.ServerConstants
import rs09.game.content.global.shops.Shops.Companion.logShop
import rs09.game.system.SystemLogger
import rs09.game.world.GameWorld
import java.lang.Integer.max
import java.lang.Integer.min
import kotlin.math.ceil
data class ShopItem(var itemId: Int, var amount: Int, val restockRate: Int = 100)
class ShopListener(val player: Player) : ContainerListener
{
var enabled = false
override fun update(c: Container?, event: ContainerEvent?) {
PacketRepository.send(ContainerPacket::class.java, ContainerContext(player, -1, -1, 92, event!!.items, false, *event.slots))
}
override fun refresh(c: Container?) {
PacketRepository.send(ContainerPacket::class.java, ContainerContext(player, -1, -1, 92, c!!.toArray(), c.capacity(), false))
}
}
class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean = false, val currency: Int = Items.COINS_995, val highAlch: Boolean = false)
{
val stockInstances = HashMap<Int, Container>()
val playerStock = if (general) generalPlayerStock else Container(40, ContainerType.SHOP)
private val needsUpdate = HashMap<Int, Boolean>()
private val restockRates = HashMap<Int,Int>()
init {
if(!getServerConfig().getBoolean(Shops.personalizedShops, false))
stockInstances[ServerConstants.SERVER_NAME.hashCode()] = generateStockContainer()
}
fun openFor(player: Player)
{
val cont = getContainer(player)
setInterfaceText(player, title, 620, 22)
setAttribute(player, "shop", this)
setAttribute(player, "shop-cont", cont)
openInterface(player, Components.SHOP_TEMPLATE_620)
player.interfaceManager.openSingleTab(Component(Components.SHOP_TEMPLATE_SIDE_621))
showTab(player, true)
logShop("Opening shop [Title: $title, Player: ${player.username}]")
}
fun showTab(player: Player, main: Boolean)
{
val cont = if (main) getAttribute<Container?>(player, "shop-cont", null) ?: return else playerStock
if(!main)
{
cont.listeners.remove(listenerInstances[player.username.hashCode()])
playerStock.listeners.add(listenerInstances[player.username.hashCode()])
}
else
{
playerStock.listeners.remove(listenerInstances[player.username.hashCode()])
cont.listeners.add(listenerInstances[player.username.hashCode()])
}
val settings = IfaceSettingsBuilder()
.enableOptions(0..9)
.build()
player.packetDispatch.sendIfaceSettings(settings, if (main) 23 else 24, Components.SHOP_TEMPLATE_620, 0, cont.capacity())
player.packetDispatch.sendRunScript(150, "IviiiIsssssssss", "", "", "", "", "Buy X", "Buy 10", "Buy 5", "Buy 1", "Value", -1, 0, 4, 10, 92, (620 shl 16) or if (main) 23 else 24)
player.packetDispatch.sendInterfaceConfig(620, 23, !main)
player.packetDispatch.sendInterfaceConfig(620, 24, main)
player.packetDispatch.sendInterfaceConfig(620, 29, !main)
player.packetDispatch.sendInterfaceConfig(620, 25, main)
player.packetDispatch.sendInterfaceConfig(620, 27, main)
player.packetDispatch.sendInterfaceConfig(620, 26, false)
if (!main) playerStock.refresh()
else cont.refresh()
setAttribute(player, "shop-main", main)
}
private fun getContainer(player: Player) : Container
{
val container = if(getServerConfig().getBoolean(Shops.personalizedShops, false))
stockInstances[player.username.hashCode()] ?: generateStockContainer().also { stockInstances[player.username.hashCode()] = it }
else
stockInstances[ServerConstants.SERVER_NAME.hashCode()]!!
val listener = listenerInstances[player.username.hashCode()]
if(listener != null && listener.player != player)
{
container.listeners.remove(listener)
}
if(listener == null || listener.player != player)
{
listenerInstances[player.username.hashCode()] = ShopListener(player)
}
return container
}
private fun generateStockContainer(): Container
{
val container = Container(40, ContainerType.SHOP)
for(item in stock) {
container.add(Item(item.itemId,item.amount))
restockRates[item.itemId] = item.restockRate
}
return container
}
fun restock()
{
stockInstances.filter { needsUpdate[it.key] == true }.forEach{ (player,cont) ->
for(i in 0 until cont.capacity())
{
if(stock.size < i + 1) break
if(GameWorld.ticks % stock[i].restockRate != 0) continue
if(cont[i].amount < stock[i].amount){
cont[i].amount++
cont.event.flag(i, cont[i])
}
else if(cont[i].amount > stock[i].amount){
cont[i].amount--
cont.event.flag(i, cont[i])
}
if(cont[i].amount != stock[i].amount) needsUpdate[player] = true
}
cont.update()
}
}
fun getBuyPrice(player: Player, slot: Int): Item
{
val isMainStock = getAttribute(player, "shop-main", true)
val cont = if (isMainStock) getAttribute<Container?>(player, "shop-cont", null) ?: return Item(-1,-1) else playerStock
val item = cont[slot]
val price = when(currency)
{
Items.TOKKUL_6529 -> item.definition.getConfiguration("tokkul_price", 1)
Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration("archery_ticket_price", 1)
else -> getGPCost(Item(item.id, 1), if (isMainStock) stock[item.slot].amount else playerStock[slot].amount, if (isMainStock) item.amount else playerStock[slot].amount)
}
return Item(currency, price)
}
fun getSellPrice(player: Player, slot: Int): Pair<Container?,Item>
{
val shopCont = getAttribute<Container?>(player, "shop-cont", null) ?: return Pair(null, Item(-1,-1))
val item = player.inventory[slot]
var (isPlayerStock, shopSlot) = getStockSlot(item.id)
val stockAmt =
if(isPlayerStock)
0
else{
if(shopSlot != -1) stock[shopSlot].amount
else 0
}
val currentAmt =
if(isPlayerStock) playerStock.getAmount(item.id)
else {
if(shopSlot != -1) shopCont[shopSlot].amount
else {
isPlayerStock = true
0
}
}
val price = when(currency)
{
Items.TOKKUL_6529 -> item.definition.getConfiguration("tokkul_price", 1)
Items.ARCHERY_TICKET_1464 -> item.definition.getConfiguration("archery_ticket_price", 1)
else -> getGPSell(Item(item.id, 1), stockAmt, currentAmt)
}
if(!general && stockAmt == 0 && shopSlot == -1)
{
return Pair(null, Item(-1,-1))
}
return Pair(if (isPlayerStock) playerStock else shopCont, Item(currency, price))
}
private fun getGPCost(item: Item, stockAmount: Int, currentAmt: Int): Int{
var mod: Int
mod = if(stockAmount == 0) 100
else if(currentAmt == 0) 130
else if(currentAmt >= stockAmount) 100
else 130 - (130 - 100) * currentAmt / stockAmount
if(mod < 1) mod = 1
mod = max(100, min(130, mod))
val price: Int = ceil(item.definition.value * mod.toDouble() / 100.0).toInt()
/* if(player.getVarp(532) == 6529){
price = 3 * price / 2
}*/
return max(price, 1)
}
private fun getGPSell(item: Item, stockAmount: Int, currentAmt: Int): Int{
if(!item.definition.isUnnoted)
item.id = item.noteChange
var mod: Int
mod = if(stockAmount == 0) 70
else if(currentAmt == 0) 100
else if(currentAmt >= stockAmount) 70
else 100 - (100 - 70) * currentAmt / stockAmount
if(mod < 1) mod = 1
mod = max(70, min(100, mod))
var base = if (highAlch) item.definition.getAlchemyValue(true) else item.definition.value
base = max(base, item.definition.value)
val price: Int = ceil(base * mod.toDouble() / 100.0).toInt()
return max(price, 1)
}
fun buy(player: Player, slot: Int, amount: Int)
{
if(amount !in 1..Integer.MAX_VALUE) return
val isMainStock = getAttribute(player, "shop-main", false)
if(!isMainStock && player.ironmanManager.isIronman)
{
sendDialogue(player, "As an ironman, you cannot buy from player stock in shops.")
return
}
val cont = if (isMainStock) getAttribute<Container?>(player, "shop-cont", null) ?: return else playerStock
val inStock = cont[slot]
val item = Item(inStock.id, amount)
if(inStock.amount < amount)
item.amount = inStock.amount
if(inStock.amount > stock[slot].amount && !getServerConfig().getBoolean(Shops.personalizedShops, false))
{
sendDialogue(player, "As an ironman, you cannot buy overstocked items from shops.")
return
}
val cost = getBuyPrice(player, slot)
if(cost.id == -1) sendMessage(player, "This shop cannot sell that item.").also { return }
if(currency == Items.COINS_995){
var amt = item.amount
var inStockAmt = inStock.amount
while(amt-- > 1)
cost.amount += getGPCost(Item(item.id, 1), if (isMainStock) stock[slot].amount else playerStock[slot].amount, --inStockAmt)
} else {
cost.amount = cost.amount * item.amount
}
if(inInventory(player, cost.id, cost.amount))
{
if(removeItem(player, cost))
{
if(!hasSpaceFor(player, item)) {
addItem(player, cost.id, cost.amount)
sendMessage(player, "You don't have enough inventory space to buy that many.")
return
}
if(!isMainStock && cont[slot].amount - item.amount == 0)
{
cont.remove(cont[slot], false)
cont.refresh()
}
else {
cont[slot].amount -= item.amount
cont.event.flag(slot, cont[slot])
cont.update()
}
addItem(player, item.id, item.amount)
if(getServerConfig().getBoolean(Shops.personalizedShops, false)){
needsUpdate[player.username.hashCode()] = true
} else {
needsUpdate[ServerConstants.SERVER_NAME.hashCode()] = true
}
}
}
else
{
sendMessage(player, "You don't have enough ${cost.name.toLowerCase()} to buy that many.")
}
}
fun sell(player: Player, slot: Int, amount: Int)
{
if(amount !in 1..Integer.MAX_VALUE) return
val playerInventory = player.inventory[slot]
if(playerInventory.id in intArrayOf(Items.COINS_995, Items.TOKKUL_6529, Items.ARCHERY_TICKET_1464))
{
sendMessage(player, "You can't sell currency to a shop.")
return
}
val item = Item(playerInventory.id, amount)
val (container,profit) = getSellPrice(player, slot)
if(profit.amount == -1) sendMessage(player, "This item can't be sold to this shop.").also { return }
if(amount > player.inventory.getAmount(item.id))
item.amount = player.inventory.getAmount(item.id)
if(currency == Items.COINS_995 && item.amount > 1){
val id = if(!item.definition.isUnnoted) item.noteChange else item.id
val (isPlayerStock, shopSlot) = getStockSlot(id)
var amt = item.amount
var inStockAmt = container!![shopSlot]?.amount ?: playerStock.getAmount(id)
while(amt-- > 1)
profit.amount += getGPSell(Item(item.id, 1), if (isPlayerStock) 0 else stock[shopSlot].amount, ++inStockAmt)
} else {
profit.amount = profit.amount * item.amount
}
if(removeItem(player, item))
{
if(!hasSpaceFor(player, profit)){
sendMessage(player, "You don't have enough space to do that.")
addItem(player, item.id, item.amount)
return
}
if(container == playerStock && getAttribute(player, "shop-main", false)){
showTab(player, false)
}
else if(!getAttribute(player, "shop-main", false) && container != playerStock)
{
showTab(player, true)
}
addItem(player, profit.id, profit.amount)
if(!item.definition.isUnnoted)
{
item.id = item.noteChange
}
container?.add(item)
container?.refresh()
if(getServerConfig().getBoolean(Shops.personalizedShops, false)){
needsUpdate[player.username.hashCode()] = true
} else {
needsUpdate[ServerConstants.SERVER_NAME.hashCode()] = true
}
}
}
fun getStockSlot(itemId: Int): Pair<Boolean, Int>
{
var shopSlot: Int = -1
var isPlayerStock = false
val notechange = itemDefinition(itemId).noteId
for((stockSlot, shopItem) in stock.withIndex())
{
if(shopItem.itemId == itemId || shopItem.itemId == notechange)
shopSlot = stockSlot
}
if(shopSlot == -1)
{
for((stockSlot, playerStockItem) in playerStock.toArray().withIndex())
{
if(playerStockItem == null) continue
if(playerStockItem.id == itemId || playerStockItem.id == notechange) {
shopSlot = stockSlot
isPlayerStock = true
}
}
}
return Pair(isPlayerStock, shopSlot)
}
companion object {
//General stores globally share player stock (weird quirk, right?)
val generalPlayerStock = Container(40, ContainerType.SHOP)
val listenerInstances = HashMap<Int, ShopListener>()
}
}
@@ -0,0 +1,256 @@
package rs09.game.content.global.shops
import api.*
import core.game.content.dialogue.FacialExpression
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.skill.crafting.TanningProduct
import org.json.simple.JSONArray
import org.json.simple.JSONObject
import org.json.simple.parser.JSONParser
import org.rs09.consts.Components
import org.rs09.consts.NPCs
import rs09.ServerConstants
import rs09.game.interaction.InteractionListener
import rs09.game.interaction.InterfaceListener
import rs09.game.system.SystemLogger
import rs09.tools.END_DIALOGUE
import java.io.FileReader
/**
* The "controller" class for shops. Handles opening shops from various NPC interactions and updating stock, etc.
* Note: If you wish to enable personalized shops, add and set the personalized_shops entry to true in the world section of the server config.
* ex:
* ```toml
* [world]
* personalized_shops = true
* ```
*/
class Shops : StartupListener, TickListener, InteractionListener, InterfaceListener, Commands {
companion object {
@JvmStatic val personalizedShops = "world.personalized_shops"
@JvmStatic val shopsById = HashMap<Int,Shop>()
@JvmStatic val shopsByNpc = HashMap<Int,Shop>()
@JvmStatic fun openId(player: Player, id: Int)
{
shopsById[id]?.openFor(player)
}
fun logShop(msg: String)
{
SystemLogger.logInfo("[SHOPS] $msg")
}
}
override fun startup() {
val path = ServerConstants.CONFIG_PATH + "shops.json"
var shopCount = 0
logShop("Using JSON path: $path")
val reader = FileReader(path)
val data = JSONParser().parse(reader) as JSONArray
fun parseStock(stock: String, id: Int): ArrayList<ShopItem>{
val items = ArrayList<ShopItem>()
val idsInStock = HashMap<Int, Boolean>()
if(stock.isEmpty()){
return items
}
stock.split('-').map {
val tokens = it.replace("{", "").replace("}", "").split(",".toRegex()).toTypedArray()
var amount = tokens[1].trim()
if(amount == "inf")
amount = "-1"
val item = tokens[0].toInt()
if(idsInStock[item] != null)
SystemLogger.logWarn("[SHOPS] MALFORMED STOCK IN SHOP ID $id FOR ITEM $item")
else
items.add(ShopItem(item, amount.toInt(), tokens.getOrNull(2)?.toIntOrNull() ?: 100))
}
return items
}
for(rawShop in data)
{
var shop: Shop?
val shopData = rawShop as JSONObject
val id = shopData["id"].toString().toInt()
val title = shopData["title"].toString()
val general = shopData["general_store"].toString().toBoolean()
val stock = parseStock(shopData["stock"].toString(), id).toTypedArray()
val npcs = if(shopData["npcs"].toString().isNotBlank()) shopData["npcs"].toString().split(",").map { it.toInt() }.toIntArray() else intArrayOf()
val currency = shopData["currency"].toString().toInt()
val highAlch = shopData["high_alch"].toString() == "1"
shop = Shop(title, stock, general, currency, highAlch)
npcs.map { shopsByNpc[it] = shop }
shopsById[id] = shop
++shopCount
}
logShop("Parsed $shopCount shops.")
}
override fun tick() {
shopsById.values.forEach(Shop::restock)
}
override fun defineListeners() {
on(NPC, "trade", "shop"){player, node ->
val npc = node as NPC
if (npc.id == 2824) {
TanningProduct.open(player, 2824)
return@on true
}
if (npc.id == 7601) {
openInterface(player, 732)
return@on true
}
shopsByNpc[npc.id]?.openFor(player) ?: return@on false
return@on true
}
on(NPCs.SIEGFRIED_ERKLE_933, NPC, "trade"){ player, node ->
val points = getQP(player)
if(points < 40){
sendNPCDialogue(player, NPCs.SIEGFRIED_ERKLE_933, "I'm sorry, adventurer, but you need 40 quest points to buy from me.")
return@on true
}
shopsByNpc[node.id]?.openFor(player)
return@on true
}
on(NPCs.FUR_TRADER_1316, NPC,"trade") { player, node ->
if (!isQuestComplete(player, "Fremennik Trials")) {
sendNPCDialogue(player, NPCs.FUR_TRADER_1316, "I don't sell to outerlanders.", FacialExpression.ANNOYED).also { END_DIALOGUE }
} else {
shopsByNpc[node.id]?.openFor(player)
}
return@on true
}
on(NPCs.FISH_MONGER_1315, NPC,"trade") { player, node ->
if (!isQuestComplete(player, "Fremennik Trials")) {
sendNPCDialogue(player, NPCs.FISH_MONGER_1315, "I don't sell to outerlanders.", FacialExpression.ANNOYED).also { END_DIALOGUE }
} else {
shopsByNpc[node.id]?.openFor(player)
}
return@on true
}
}
override fun defineInterfaceListeners() {
on(Components.SHOP_TEMPLATE_620){player, _, opcode, buttonID, slot, _ ->
val OP_VALUE = 155
val OP_BUY_1 = 196
val OP_BUY_5 = 124
val OP_BUY_10 = 199
val OP_BUY_X = 234
val OP_EXAMINE = 9
val shop = getAttribute<Shop?>(player, "shop", null) ?: return@on false
val isMainStock = getAttribute(player, "shop-main", true)
when(buttonID)
{
26 -> shop.showTab(player, false).also { return@on true }
25 -> shop.showTab(player, true).also { return@on true }
27,29 -> return@on true
}
val price = shop.getBuyPrice(player, slot)
when(opcode)
{
OP_VALUE -> sendMessage(player, "${getItemName(if (isMainStock) shop.stock[slot].itemId else shop.playerStock[slot].id)}: This item currently costs ${price.amount} ${price.name.toLowerCase()}.")
OP_BUY_1 -> shop.buy(player, slot, 1)
OP_BUY_5 -> shop.buy(player, slot, 5)
OP_BUY_10 -> shop.buy(player, slot, 10)
OP_BUY_X -> sendInputDialogue(player, true, "Enter the amount to buy:"){value ->
val amt = value as Int
shop.buy(player, slot, amt)
}
OP_EXAMINE -> sendMessage(player, itemDefinition(if (isMainStock) shop.stock[slot].itemId else shop.playerStock[slot].id).examine)
}
return@on true
}
onOpen(Components.SHOP_TEMPLATE_SIDE_621) {player, _ ->
val settings = IfaceSettingsBuilder()
.enableOptions(0 until 9)
.build()
player.packetDispatch.sendIfaceSettings(settings, 0, 621, 0, 28)
player.packetDispatch.sendRunScript(150, "IviiiIsssssssss", "", "", "", "", "Sell X", "Sell 10", "Sell 5", "Sell 1", "Value", -1, 0, 7, 4, 93, 621 shl 16);
return@onOpen true
}
onClose(Components.SHOP_TEMPLATE_620) { player, _ ->
val shop = getAttribute<Shop?>(player, "shop", null) ?: return@onClose true
val listener = Shop.listenerInstances[player.username.hashCode()] ?: return@onClose true
if(getServerConfig().getBoolean(personalizedShops, false))
shop.stockInstances[player.username.hashCode()]?.listeners?.remove(listener)
else
shop.stockInstances[ServerConstants.SERVER_NAME.hashCode()]!!.listeners.remove(listener)
shop.playerStock.listeners.remove(listener)
player.interfaceManager.closeSingleTab()
return@onClose true
}
on(Components.SHOP_TEMPLATE_SIDE_621){player, component, opcode, buttonID, slot, itemID ->
val OP_VALUE = 155
val OP_SELL_1 = 196
val OP_SELL_5 = 124
val OP_SELL_10 = 199
val OP_SELL_X = 234
val shop = getAttribute<Shop?>(player, "shop", null) ?: return@on false
val (_,price) = shop.getSellPrice(player, slot)
val valueMsg = if(price.amount == -1) "This shop will not buy that item." else "${player.inventory[slot].name}: This shop will buy this item for ${price.amount} ${price.name.toLowerCase()}."
when(opcode)
{
OP_VALUE -> sendMessage(player, valueMsg)
OP_SELL_1 -> shop.sell(player, slot, 1)
OP_SELL_5 -> shop.sell(player, slot, 5)
OP_SELL_10 -> shop.sell(player, slot, 10)
OP_SELL_X -> sendInputDialogue(player, true, "Enter the amount to sell:"){value ->
val amt = value as Int
shop.sell(player, slot, amt)
}
}
return@on true
}
}
override fun defineDestinationOverrides() {
setDest(NPC,"trade","shop"){_,node ->
val npc = node as NPC
if (npc.getAttribute("facing_booth", false)) {
val offsetX = npc.direction.stepX shl 1
val offsetY = npc.direction.stepY shl 1
return@setDest npc.location.transform(offsetX, offsetY, 0)
}
return@setDest node.location
}
}
override fun defineCommands() {
define("openshop") { player, args ->
if(args.size < 2) reject(player, "Usage: ::openshop shopId")
val shopId = args[1].toInt()
shopsById[shopId]?.openFor(player)
}
define("shopscript") { player, args ->
val arg1 = args[1].toInt()
player.packetDispatch.sendRunScript(25, "vg", arg1, 92) //Run CS2 script 25, with args 868? and 92(our container id)
}
}
}
@@ -48,7 +48,7 @@ class GiftRollPlugin : XPGainPlugin() {
}
}
class XMASMboxHandler : InteractionListener() {
class XMASMboxHandler : InteractionListener {
val MBOX = Items.MYSTERY_BOX_6199
override fun defineListeners() {
@@ -7,7 +7,7 @@ import org.rs09.consts.Items
import org.rs09.consts.NPCs
import rs09.game.interaction.InteractionListener
class EasterEventListeners : InteractionListener() {
class EasterEventListeners : InteractionListener {
val EGG_ATTRIBUTE = "/save:easter:eggs"
val eggs = intArrayOf(Items.EASTER_EGG_11027, Items.EASTER_EGG_11028, Items.EASTER_EGG_11029, Items.EASTER_EGG_11030)
@@ -19,7 +19,7 @@ import rs09.game.interaction.InteractionListener
import rs09.game.world.GameWorld
import rs09.tools.END_DIALOGUE
class TrickOrTreatHandler : InteractionListener() {
class TrickOrTreatHandler : InteractionListener {
override fun defineListeners() {
on(NPC, "trick-or-treat"){player, node ->
val hasDone5 = getDailyTrickOrTreats(player) == 5
@@ -13,7 +13,7 @@ import core.game.content.quest.PluginInteraction
import core.game.content.quest.PluginInteractionManager
import rs09.game.interaction.InteractionListener
class PenguinSpyingHandler : InteractionListener(){
class PenguinSpyingHandler : InteractionListener{
override fun defineListeners() {
on(PENGUINS, NPC, "spy-on"){player, node ->
@@ -71,15 +71,15 @@ class ShootingStar(var level: ShootingStarType = ShootingStarType.values().rando
starSprite.location = starObject.location
starSprite.init()
spriteSpawned = true
ShootingStarEvent.getStoreFile().clear()
ShootingStarPlugin.getStoreFile().clear()
return
}
level = getNextType()
maxDust = level.totalStardust
dustLeft = level.totalStardust
ShootingStarEvent.getStoreFile()["level"] = level.ordinal
ShootingStarEvent.getStoreFile()["isDiscovered"] = isDiscovered
ShootingStarPlugin.getStoreFile()["level"] = level.ordinal
ShootingStarPlugin.getStoreFile()["isDiscovered"] = isDiscovered
val newStar = Scenery(level.objectId, starObject.location)
SceneryBuilder.replace(starObject, newStar)
@@ -114,10 +114,10 @@ class ShootingStar(var level: ShootingStarType = ShootingStarType.values().rando
* Rebuilds some of the variables with new information.
*/
fun rebuildVars(){
if(firstStar && ShootingStarEvent.getStoreFile().isNotEmpty()){
level = ShootingStarType.values()[ShootingStarEvent.getStoreFile().getInt("level")]
location = ShootingStarEvent.getStoreFile().getString("location")
isDiscovered = ShootingStarEvent.getStoreFile().getBoolean("isDiscovered")
if(firstStar && ShootingStarPlugin.getStoreFile().isNotEmpty()){
level = ShootingStarType.values()[ShootingStarPlugin.getStoreFile().getInt("level")]
location = ShootingStarPlugin.getStoreFile().getString("location")
isDiscovered = ShootingStarPlugin.getStoreFile().getBoolean("isDiscovered")
} else {
level = ShootingStarType.values().random()
location = crash_locations.entries.random().key
@@ -128,9 +128,9 @@ class ShootingStar(var level: ShootingStarType = ShootingStarType.values().rando
dustLeft = level.totalStardust
starObject = Scenery(level.objectId, crash_locations.get(location))
ShootingStarEvent.getStoreFile()["level"] = level.ordinal
ShootingStarEvent.getStoreFile()["location"] = location
ShootingStarEvent.getStoreFile()["isDiscovered"] = false
ShootingStarPlugin.getStoreFile()["level"] = level.ordinal
ShootingStarPlugin.getStoreFile()["location"] = location
ShootingStarPlugin.getStoreFile()["isDiscovered"] = false
ticks = 0
firstStar = false
@@ -1,40 +0,0 @@
package rs09.game.content.global.worldevents.shootingstar
import core.game.node.entity.player.Player
import core.game.system.command.CommandSet
import core.plugin.Plugin
import rs09.game.content.global.worldevents.WorldEvents
import rs09.game.system.command.CommandPlugin
import java.util.concurrent.TimeUnit
/**
* A few assorted commands for shooting stars.
*/
class ShootingStarCommands : CommandPlugin(){
override fun newInstance(arg: Any?): Plugin<Any?>{
link(CommandSet.DEVELOPER)
return this
}
override fun parse(player: Player?, name: String?, args: Array<String?>?): Boolean {
val star = (WorldEvents.get("shooting-stars") as ShootingStarEvent).star
when (name) {
"tostar" -> {
player!!.teleport(star.starObject.location.transform(1, 1, 0))
}
"submit" -> {
star.fire()
}
"resettime" -> player!!.savedData.globalData.starSpriteDelay = 0L
"stardust" -> {
val dust = 8
println("Cosmic Runes: " + 0.76 * dust)
println("Astral runes: " + 0.26 * dust)
println("Gold ores: " + 0.1 * dust)
println("GP: " + 250.1 * dust)
}
"setsprite" -> player!!.savedData.globalData.starSpriteDelay = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1)
}
return true
}
}
@@ -1,92 +0,0 @@
package rs09.game.content.global.worldevents.shootingstar
import core.game.content.global.worldevents.shootingstar.ScoreboardHandler
import core.game.content.global.worldevents.shootingstar.ShootingStarScoreboard
import core.game.content.global.worldevents.shootingstar.StarChartPlugin
import core.game.system.task.Pulse
import org.json.simple.JSONObject
import rs09.ServerStore
import rs09.game.content.global.worldevents.PluginSet
import rs09.game.content.global.worldevents.WorldEvent
import rs09.game.content.global.worldevents.WorldEvents
import rs09.game.world.GameWorld
/**
* The world event class for shooting stars. Keeps track of event-related data like the ShootingStar instance.
* Also handles spawning new shooting stars based on time.
* @author Ceikry
*/
class ShootingStarEvent : WorldEvent("shooting-stars") {
val star = ShootingStar()
val tickDelay = if(GameWorld.settings?.isDevMode == true) 200 else 25000
override fun initialize() {
plugins = PluginSet(
ScoreboardHandler(),
ShootingStarScoreboard(),
StarChartPlugin(),
ShootingStarCommands(),
StarSpriteDialogue(),
ShootingStarLogin()
)
super.initialize()
GameWorld.Pulser.submit(StarPulse())
log("Shooting Star event has been initialized.")
}
override fun checkTrigger(): Boolean {
/**
* Spawn a new star only if: star's ticks are greater than the tickDelay and star sprite is not spawned OR,
* neither the star or the sprite are spawned.
*/
star.ticks += 10
if ((star.ticks >= tickDelay && !star.spriteSpawned) || (!star.isSpawned && !star.spriteSpawned)) {
return true
}
/**
* Clear the sprite when we have passed the tickDelay + (1/3) of the tickDelay.
* This gives players a little extra time before the star respawns to run to the bank and grab
* stardust or whatever. Once the sprite is cleared a new star will be allowed to spawn.
*/
val maxDelay = tickDelay + (tickDelay / 3)
if(star.ticks > maxDelay && star.spriteSpawned){
star.clearSprite()
}
return false
}
override fun checkActive(): Boolean {
return true //this event is always active.
}
override fun fireEvent() {
log("Fired new shooting star event.")
star.fire()
}
/**
* Handles checking star status and spawning new ones if necessary.
*/
class StarPulse : Pulse(10){
override fun pulse(): Boolean {
val event = WorldEvents.get("shooting-stars")
event ?: return true
if(event.checkTrigger()){
event.fireEvent()
}
return false //always returns false because it needs to run forever.
}
}
companion object {
fun getStoreFile() : JSONObject {
return ServerStore.getArchive("shooting-star")
}
}
}
@@ -1,29 +0,0 @@
package rs09.game.content.global.worldevents.shootingstar
import core.game.node.entity.player.Player
import core.plugin.Plugin
import core.plugin.PluginManifest
import core.plugin.PluginType
import rs09.game.content.global.worldevents.WorldEvents
/**
* A plugin that handles the message a player receives on login pertaining to shooting stars.
*/
@PluginManifest(type = PluginType.LOGIN)
class ShootingStarLogin : Plugin<Player?> {
@Throws(Throwable::class)
override fun newInstance(arg: Player?): Plugin<Player?>? {
if (arg is Player) {
val star = (WorldEvents.get("shooting-stars") as ShootingStarEvent).star
if (star.isSpawned) {
arg.sendMessage("<img=12><col=CC6600>News: A shooting star (Level " + (star.level.ordinal + 1).toString() + ") has just crashed near the " + star.location + "!")
}
return this
}
return this
}
override fun fireEvent(identifier: String, vararg args: Any): Any {
return Unit
}
}
@@ -1,7 +1,6 @@
package rs09.game.content.global.worldevents.shootingstar
import api.*
import core.game.content.global.worldevents.shootingstar.ScoreboardManager
import core.game.node.scenery.Scenery
import core.game.node.entity.player.Player
import core.game.node.entity.skill.SkillPulse
@@ -50,7 +49,7 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin
player.incrementAttribute("/save:shooting-star:bonus-xp", bonusXp)
Repository.sendNews(player.username + " is the discoverer of the crashed star near " + star.location + "!")
player.sendMessage("You have ${player.skills.experienceMutiplier * player.getAttribute("shooting-star:bonus-xp", 0).toDouble()} bonus xp towards mining stardust.")
ScoreboardManager.submit(player)
ShootingStarPlugin.submitScoreBoard(player)
star.isDiscovered = true
return player.skills.getLevel(Skills.MINING) >= star.miningLevel
}
@@ -63,7 +62,7 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin
player.packetDispatch.sendMessage("You do not have a pickaxe to use.")
return false
}
if (player.inventory.freeSlots() < 1 && !player.inventory.contains(ShootingStarOptionHandler.STAR_DUST, 1)) {
if (player.inventory.freeSlots() < 1 && !player.inventory.contains(ShootingStarPlugin.STAR_DUST, 1)) {
player.dialogueInterpreter.sendDialogue("Your inventory is too full to hold any more stardust.")
return false
}
@@ -98,8 +97,8 @@ class ShootingStarMiningPulse(player: Player?, node: Scenery?, val star: Shootin
}
player.skills.addExperience(Skills.MINING, xp)
if (ShootingStarOptionHandler.getStarDust(player) < 200) {
player.inventory.add(Item(ShootingStarOptionHandler.STAR_DUST, 1))
if (ShootingStarPlugin.getStarDust(player) < 200) {
player.inventory.add(Item(ShootingStarPlugin.STAR_DUST, 1))
}
if(!inInventory(player, Items.ANCIENT_BLUEPRINT_14651) && !inBank(player, Items.ANCIENT_BLUEPRINT_14651)){
rollBlueprint(player)
@@ -1,39 +0,0 @@
package rs09.game.content.global.worldevents.shootingstar
import core.game.node.entity.player.Player
import rs09.game.content.global.worldevents.WorldEvents
import rs09.game.interaction.InteractionListener
/**
* Option handlers for the various shooting star objects.
*/
class ShootingStarOptionHandler : InteractionListener() {
val SHOOTING_STARS = ShootingStarType.values().map(ShootingStarType::objectId).toIntArray()
override fun defineListeners() {
on(SHOOTING_STARS,SCENERY,"mine"){ player, _ ->
val star = (WorldEvents.get("shooting-stars") as ShootingStarEvent).star
star.mine(player)
return@on true
}
on(SHOOTING_STARS,SCENERY,"prospect"){ player, _ ->
val star = (WorldEvents.get("shooting-stars") as ShootingStarEvent).star
star.prospect(player)
return@on true
}
}
companion object {
const val STAR_DUST = 13727
/**
* Gets the star dust amount for a player.
* @param player The player.
* @return The stardust amount.
*/
fun getStarDust(player: Player): Int {
return player.inventory.getAmount(STAR_DUST) + player.bank.getAmount(STAR_DUST)
}
}
}
@@ -0,0 +1,176 @@
package rs09.game.content.global.worldevents.shootingstar
import api.*
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.TeleportManager
import core.game.node.entity.skill.Skills
import org.json.simple.JSONObject
import org.rs09.consts.Items
import org.rs09.consts.Scenery
import rs09.ServerStore
import rs09.ServerStore.Companion.getBoolean
import rs09.game.content.dialogue.DialogueFile
import rs09.game.interaction.InteractionListener
import rs09.game.system.SystemLogger
import rs09.game.system.command.Privilege
import rs09.game.world.GameWorld
import rs09.tools.secondsToTicks
class ShootingStarPlugin : LoginListener, InteractionListener, TickListener, Commands, StartupListener {
override fun login(player: Player) {
if(star.isSpawned && !star.spriteSpawned)
sendMessage(player, "<img=12><col=CC6600>News: A shooting star (Level ${star.level.ordinal + 1}) has just crashed near the ${star.location}!")
}
override fun tick() {
++star.ticks
val maxDelay = tickDelay + (tickDelay / 3)
if(star.ticks > maxDelay && star.spriteSpawned){
star.clearSprite()
}
if ((star.ticks >= tickDelay && !star.spriteSpawned) || (!star.isSpawned && !star.spriteSpawned)) {
star.fire()
}
}
override fun defineListeners() {
on(Scenery.SHOOTING_STAR_NOTICEBOARD_38669, SCENERY, "read") { player, _ ->
var index = 0
scoreboardEntries.forEach { entry ->
val timeElapsed = secondsToTicks(GameWorld.ticks - entry.time) / 60
setInterfaceText(player, "$timeElapsed minutes ago", scoreboardIface, index + 6)
setInterfaceText(player, entry.player, scoreboardIface, index + 11)
++index
}
openInterface(player, scoreboardIface)
return@on true
}
on(SHOOTING_STARS,SCENERY,"mine"){ player, _ ->
star.mine(player)
return@on true
}
on(SHOOTING_STARS,SCENERY,"prospect"){ player, _ ->
star.prospect(player)
return@on true
}
on(RING, ITEM, "rub", "operate"){player, node ->
if(getRingStoreFile().getBoolean(player.username.toLowerCase())){
sendDialogue(player, "The ring is still recharging.")
return@on true
}
val condition: (Player) -> Boolean = when(star.location.toLowerCase()){
"canifis bank" -> { p -> p.questRepository.isComplete("Priest in Peril")}
"crafting guild" -> {p -> hasLevelStat(p, Skills.CRAFTING, 40) }
"south crandor mining site" -> {p -> p.questRepository.isComplete("Dragon Slayer")}
else -> {_ -> true}
}
if(!condition.invoke(player) || player.skullManager.isWilderness){
sendDialogue(player, "Magical forces prevent your teleportation.")
return@on true
}
val shouldWarn = when(star.location){
"North Edgeville mining site",
"Southern wilderness mine",
"Pirates' Hideout mine",
"Lava Maze mining site",
"Mage Arena bank" -> true
else -> false
}
openDialogue(player, RingDialogue(shouldWarn, star))
return@on true
}
}
override fun defineCommands() {
define("tostar", Privilege.ADMIN) { player, _ ->
teleport(player, star.starObject.location.transform(1,1,0))
}
define("submit", Privilege.ADMIN) { _, _ ->
star.fire()
}
define("resetsprite", Privilege.ADMIN) { player, _ ->
player.savedData.globalData.starSpriteDelay = 0L
}
}
override fun startup() {
SystemLogger.logInfo("Shooting Stars initialized.")
}
private data class ScoreboardEntry(val player: String, val time: Int)
private class RingDialogue(val shouldWarn: Boolean, val star: ShootingStar) : DialogueFile(){
override fun handle(componentID: Int, buttonID: Int) {
if(shouldWarn){
when(stage) {
0 -> dialogue("WARNING: That mining site is located in the wilderness.").also { stage++ }
1 -> player!!.dialogueInterpreter.sendOptions("Continue?","Yes","No").also { stage++ }
2 -> when(buttonID){
1 -> teleport(player!!, star).also { end() }
2 -> end()
}
}
} else {
when(stage){
0 -> player!!.dialogueInterpreter.sendOptions("Teleport to the Star?", "Yes", "No").also { stage++ }
1 -> when(buttonID){
1 -> teleport(player!!, star).also { end() }
2 -> end()
}
}
}
}
fun teleport(player: Player, star: ShootingStar){
teleport(player, star.crash_locations[star.location]!!.transform(0, -1, 0), TeleportManager.TeleportType.MINIGAME)
getRingStoreFile()[player.username.toLowerCase()] = true
}
}
companion object {
private val star = ShootingStar()
private val tickDelay = if(GameWorld.settings?.isDevMode == true) 200 else 25000
private val scoreboardEntries = ArrayList<ScoreboardEntry>()
private val scoreboardIface = 787
val SHOOTING_STARS = ShootingStarType.values().map(ShootingStarType::objectId).toIntArray()
val STAR_DUST = Items.STARDUST_13727
val RING = Items.RING_OF_THE_STAR_SPRITE_14652
@JvmStatic fun submitScoreBoard(player: Player)
{
if(scoreboardEntries.size == 5)
scoreboardEntries.removeAt(0)
scoreboardEntries.add(ScoreboardEntry(player.username, GameWorld.ticks))
}
@JvmStatic fun getStar(): ShootingStar
{
return star
}
@JvmStatic fun getStoreFile() : JSONObject {
return ServerStore.getArchive("shooting-star")
}
@JvmStatic fun getRingStoreFile() : JSONObject {
return ServerStore.getArchive("daily-star-ring")
}
fun getStarDust(player: Player): Int {
return player.inventory.getAmount(STAR_DUST) + player.bank.getAmount(STAR_DUST)
}
}
}
@@ -7,6 +7,7 @@ import core.game.content.dialogue.FacialExpression
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.item.Item
import core.plugin.Initializable
import core.tools.RandomFunction
import org.json.simple.JSONObject
import org.rs09.consts.Items
@@ -21,6 +22,7 @@ import java.util.concurrent.TimeUnit
/**
* Dialogue for the star sprite.
*/
@Initializable
class StarSpriteDialogue(player: Player? = null) : DialoguePlugin(player) {
/**
@@ -70,7 +72,7 @@ class StarSpriteDialogue(player: Player? = null) : DialoguePlugin(player) {
} else if (inInventory(player, Items.ANCIENT_BLUEPRINT_14651) && getAttribute(player, "star-ring:bp-shown", false)) {
playerl(FacialExpression.HALF_ASKING, "So about those rings...")
stage = 2000
} else if (getStoreFile().getBoolean(player.username.toLowerCase()) || !player.getInventory().contains(ShootingStarOptionHandler.STAR_DUST, 1)) {
} else if (getStoreFile().getBoolean(player.username.toLowerCase()) || !player.getInventory().contains(ShootingStarPlugin.STAR_DUST, 1)) {
npc("Hello, strange creature.")
stage = 0
} else {
@@ -185,8 +187,8 @@ class StarSpriteDialogue(player: Player? = null) : DialoguePlugin(player) {
41 -> end()
50 -> {
val wearingRing = inEquipment(player, Items.RING_OF_THE_STAR_SPRITE_14652)
val dust = if (player.getInventory().getAmount(ShootingStarOptionHandler.STAR_DUST) > 200) 200 else player.getInventory().getAmount(ShootingStarOptionHandler.STAR_DUST)
if (player.getInventory().remove(Item(ShootingStarOptionHandler.STAR_DUST, dust))) {
val dust = if (player.getInventory().getAmount(ShootingStarPlugin.STAR_DUST) > 200) 200 else player.getInventory().getAmount(ShootingStarPlugin.STAR_DUST)
if (player.getInventory().remove(Item(ShootingStarPlugin.STAR_DUST, dust))) {
val cosmicRunes = (Math.ceil(0.76 * dust) * AMPLIFIER).toInt()
val astralRunes = (Math.ceil(0.26 * dust) * AMPLIFIER).toInt()
val goldOre = (Math.ceil(0.1 * dust) * AMPLIFIER).toInt()
@@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit
* Handles the work-for actions for the NPCs
* @author Ceikry
*/
class WorkForInteractionListener : InteractionListener(), LoginListener {
class WorkForInteractionListener : InteractionListener, LoginListener {
val possibleWeaponLooks = arrayListOf(
Items.BRONZE_SCIMITAR_1321,
Items.STEEL_SCIMITAR_1325,
@@ -4,7 +4,7 @@ import api.*
import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
class DSEquipListeners : InteractionListener() {
class DSEquipListeners : InteractionListener {
private val restrictedItems = intArrayOf(
Items.RUNE_PLATEBODY_1127,
@@ -6,7 +6,7 @@ import org.rs09.consts.Items
import org.rs09.consts.NPCs
import rs09.game.interaction.InteractionListener
class JohnathonAntiPosionInteraction: InteractionListener() {
class JohnathonAntiPosionInteraction: InteractionListener {
override fun defineListeners() {
val poisons = intArrayOf(Items.ANTIPOISON4_2446, Items.ANTIPOISON3_175, Items.ANTIPOISON2_177, Items.ANTIPOISON1_179)
@@ -32,7 +32,7 @@ fun doDoor(player: Player, scenery: Scenery) {
}
class WitchavenLeverInteraction : InteractionListener() {
class WitchavenLeverInteraction : InteractionListener {
val DOWN_ANIMATION = Animation(2140)
val UP_ANIMATION = Animation(2139)
@@ -109,7 +109,7 @@ class BrotherCedricDialogue : DialogueFile() {
* Handles BrotherCedricListener to launch the dialogue
* @author Kya
*/
class BrotherCedricListener : InteractionListener() {
class BrotherCedricListener : InteractionListener {
override fun defineListeners() {
on(NPCs.BROTHER_CEDRIC_280, NPC, "talk-to"){ player, _ ->
player.dialogueInterpreter.open(BrotherCedricDialogue(), NPC(NPCs.BROTHER_CEDRIC_280))
@@ -205,7 +205,7 @@ class BrotherOmadDialogue : DialogueFile() {
* Handles BrotherCedricListener to launch the dialogue
* @author Kya
*/
class BrotherOmadListener : InteractionListener() {
class BrotherOmadListener : InteractionListener {
override fun defineListeners() {
on(NPCs.BROTHER_OMAD_279, NPC, "talk-to"){ player, _ ->
player.dialogueInterpreter.open(BrotherOmadDialogue(), NPC(NPCs.BROTHER_OMAD_279))
@@ -34,7 +34,7 @@ class MonasteryMonkDialogue : DialogueFile() {
* Handles BrotherCedricListener to launch the dialogue
* @author Kya
*/
class MonasteryMonkListener : InteractionListener() {
class MonasteryMonkListener : InteractionListener {
override fun defineListeners() {
on(NPCs.MONK_281, NPC, "talk-to"){ player, _ ->
player.dialogueInterpreter.open(MonasteryMonkDialogue(), NPC(NPCs.MONK_281))
@@ -14,13 +14,14 @@ import org.rs09.consts.Items
import org.rs09.consts.NPCs
import rs09.game.content.dialogue.DialogueFile
import rs09.game.content.global.action.PickupHandler
import rs09.game.content.global.shops.Shops
import rs09.game.interaction.InteractionListener
import rs09.game.node.entity.npc.other.MortMyreGhastNPC
import rs09.game.system.SystemLogger
import rs09.game.system.config.ShopParser
import rs09.tools.END_DIALOGUE
class NSListeners : InteractionListener() {
class NSListeners : InteractionListener {
val GROTTO_TREE = 3517
val GROTTO_ENTRANCE = 3516
@@ -101,7 +102,7 @@ class NSListeners : InteractionListener() {
on(WISHING_WELL, SCENERY, "make-wish"){player, node ->
if(player.questRepository.isComplete("Nature Spirit") && player.questRepository.isComplete("Wolf Whistle"))
ShopParser.openUid(player, 241)
Shops.openId(player, 241)
else
sendDialogue(player, "You can't do that yet.")
@@ -10,7 +10,7 @@ import rs09.game.world.GameWorld.Pulser
import kotlin.math.abs
import kotlin.math.atan2
class HunterTalismanListener : InteractionListener() {
class HunterTalismanListener : InteractionListener {
val TALISMAN = Items.HUNTERS_TALISMAN_3696
@@ -4,9 +4,9 @@ import api.sendMessage
import core.game.container.access.BitregisterAssembler
import rs09.game.interaction.InterfaceListener
class SeerLockInterfaceListener : InterfaceListener() {
class SeerLockInterfaceListener : InterfaceListener {
override fun defineListeners() {
override fun defineInterfaceListeners() {
val LETTERONEBACK = 39
val LETTERONEFORWARD = 40
val LETTERTWOBACK = 35
@@ -16,7 +16,7 @@ import rs09.game.content.dialogue.DialogueFile
import rs09.game.interaction.InteractionListener
import rs09.tools.stringtools.RED
class SeersHouseListeners : InteractionListener() {
class SeersHouseListeners : InteractionListener {
val WESTDOOR = 4165
val EASTDOOR = 4166
@@ -30,7 +30,7 @@ import rs09.game.system.config.ItemConfigParser
import rs09.game.world.GameWorld
import rs09.game.world.GameWorld.Pulser
class TFTInteractionListeners : InteractionListener(){
class TFTInteractionListeners : InteractionListener{
val BEER = Items.BEER_1917
val WORKER = NPCs.COUNCIL_WORKMAN_1287
@@ -7,7 +7,7 @@ import core.game.node.entity.skill.gather.SkillingTool
import core.game.world.map.Location
import rs09.game.interaction.InteractionListener
class DramenTreeListener : InteractionListener() {
class DramenTreeListener : InteractionListener {
val DRAMEN_TREE = 1292
@@ -13,7 +13,7 @@ import rs09.game.world.GameWorld
* handles pickpocketing sigmund during the lost tribe quest
* @author Ceikry
*/
class PickpocketSigmund : InteractionListener(){
class PickpocketSigmund : InteractionListener{
val SIGMUND = NPCs.SIGMUND_2082
override fun defineListeners() {
@@ -6,9 +6,9 @@ import api.setInterfaceText
import core.game.world.map.Location
import rs09.game.interaction.InterfaceListener
class TTDoorCodeInterfaceListener : InterfaceListener() {
class TTDoorCodeInterfaceListener : InterfaceListener {
override fun defineListeners() {
override fun defineInterfaceListeners() {
val LETTERONEBACK = 10
val LETTERONEFORWARD = 11
val LETTERTWOBACK = 12
@@ -10,7 +10,7 @@ import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
class TribalTotemListeners : InteractionListener(){
class TribalTotemListeners : InteractionListener{
val frontDoor = 2706
val wizCrate = 2707
@@ -4,7 +4,7 @@ import core.game.world.map.Location
import org.rs09.consts.NPCs
import rs09.game.interaction.InteractionListener
class WaterfallListeners : InteractionListener(){
class WaterfallListeners : InteractionListener{
val HUDON = NPCs.HUDON_305
override fun defineListeners() {
@@ -13,7 +13,7 @@ import rs09.game.interaction.InteractionListener
* Handles tutorial-specific node interactions
* @author Ceikry
*/
class TutorialListeners : InteractionListener() {
class TutorialListeners : InteractionListener {
val GUIDE_HOUSE_DOOR = 3014
val COOKS_DOOR = 3017
val COOKS_EXIT = 3018
@@ -6,75 +6,52 @@ import core.game.node.entity.Entity
import core.game.node.entity.npc.NPC
import core.game.node.entity.player.Player
import core.game.node.entity.skill.Skills
import core.game.system.task.Pulse
import core.game.world.map.zone.MapZone
import core.game.world.map.zone.ZoneBorders
import core.game.world.map.zone.ZoneBuilder
import core.plugin.Initializable
import core.plugin.Plugin
import org.rs09.consts.NPCs
import rs09.game.content.dialogue.DialogueFile
import rs09.tools.secondsToTicks
import java.util.concurrent.TimeUnit
@Initializable
class FarmingPatchZone : MapZone("farming patch", true), Plugin<Any> {
class FarmingPatchZone : MapArea, TickListener {
private val playersInZone = hashMapOf<Player,Int>()
val playersInZone = hashMapOf<Player,Int>()
override fun configure() {
registerRegion(12083)
registerRegion(10548)
register(ZoneBorders(3594,3521,3608,3532))
submitWorldPulse(zonePulse)
override fun defineAreaBorders(): Array<ZoneBorders> {
return arrayOf(
getRegionBorders(12083),
getRegionBorders(10548),
ZoneBorders(3594,3521,3608,3532)
)
}
override fun newInstance(arg: Any?): Plugin<Any> {
ZoneBuilder.configure(this)
return this
}
override fun fireEvent(identifier: String?, vararg args: Any?): Any {
return Unit
}
override fun enter(e: Entity?): Boolean {
if(e is Player && playersInZone[e] == null && getStatLevel(e, Skills.FARMING) <= 15) {
playersInZone[e] = 0
override fun areaEnter(entity: Entity) {
if(entity is Player && playersInZone[entity] == null && getStatLevel(entity, Skills.FARMING) <= 15) {
playersInZone[entity] = 0
}
return super.enter(e)
}
override fun leave(e: Entity?, logout: Boolean): Boolean {
if(e is Player){
playersInZone.remove(e)
}
return super.enter(e)
override fun areaLeave(entity: Entity, logout: Boolean) {
if(entity is Player)
playersInZone.remove(entity)
}
val zonePulse = object : Pulse(){
override fun pulse(): Boolean {
playersInZone.toList().forEach { (player, ticks) ->
if(ticks == secondsToTicks(TimeUnit.MINUTES.toSeconds(5).toInt())){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
openDialogue(player, SpiritDialogue(true), npc)
} else if (ticks == secondsToTicks(TimeUnit.MINUTES.toSeconds(10).toInt())){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
openDialogue(player, SpiritDialogue(false), npc)
playersInZone.remove(player)
}
playersInZone[player] = ticks + 1
override fun tick() {
playersInZone.toList().forEach { (player, ticks) ->
if(ticks == 500){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
openDialogue(player, SpiritDialogue(true), npc)
} else if (ticks == 1000){
val npc = NPC(NPCs.GITHAN_7122)
npc.location = player.location
npc.init()
npc.moveStep()
npc.face(player)
openDialogue(player, SpiritDialogue(false), npc)
playersInZone.remove(player)
}
return false
playersInZone[player] = ticks + 1
}
}
@@ -94,5 +71,4 @@ class FarmingPatchZone : MapZone("farming patch", true), Plugin<Any> {
poofClear(npc ?: return)
}
}
}
@@ -34,7 +34,7 @@ import core.tools.RandomFunction
*/
val SINISTER_CHEST_HERBS = arrayOf(Item(205, 2), Item(207, 3), Item(209), Item(211), Item(213), Item(219))
public class YanilleAgilityDungeonListeners : InteractionListener() {
public class YanilleAgilityDungeonListeners : InteractionListener {
override fun defineListeners() {
ZoneBuilder.configure(YanilleAgilityDungeon());
@@ -50,7 +50,7 @@ class OrdanDialogue(player: Player? = null) : DialoguePlugin(player) {
* just comment those options out if you don't want em
* aka "WOW I CAN'T BELIEVE IT'S ALL CONDITIONALS*/
class OrdanUnnoteListener : InteractionListener() {
class OrdanUnnoteListener : InteractionListener {
val notedOre = intArrayOf(
Items.IRON_ORE_441,
Items.COPPER_ORE_437,
@@ -24,7 +24,7 @@ private val FILL_ANIM = Animation(1649)
private val WIND_ANIM = Animation(1648)
private val SCOOP_ANIM = Animation(1650)
class BoneGrinderListener : InteractionListener() {
class BoneGrinderListener : InteractionListener {
private val boneIDs = Bones.values().map { it.itemId }.toIntArray()
@@ -2,34 +2,13 @@ package rs09.game.ge
import api.*
import core.cache.def.impl.ItemDefinition
import core.game.component.CloseEvent
import core.game.component.Component
import core.game.component.InterfaceType
import core.game.container.Container
import core.game.container.ContainerEvent
import core.game.container.ContainerListener
import core.game.container.access.BitregisterAssembler
import core.game.container.access.InterfaceContainer
import core.game.ge.GEItemSet
import core.game.ge.GrandExchangeDatabase
import core.game.ge.GrandExchangeEntry
import core.game.ge.OfferState
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.audio.Audio
import core.game.node.item.Item
import core.game.system.monitor.PlayerMonitor
import core.net.packet.PacketRepository
import core.net.packet.context.ConfigContext
import core.net.packet.context.ContainerContext
import core.net.packet.out.Config
import core.net.packet.out.ContainerPacket
import org.json.simple.JSONArray
import org.json.simple.JSONObject
import org.rs09.consts.Components
import org.rs09.consts.Items
import rs09.game.ai.AIPlayer
import rs09.game.system.SystemLogger
import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.*
@@ -98,12 +77,12 @@ class GrandExchangeRecords(private val player: Player? = null) : PersistPlayer,
return
}
player.interfaceManager.openComponent(Components.STOCKCOLLECT_109)
player.packetDispatch.sendAccessMask(6, 18, 109, 0, 2)
player.packetDispatch.sendAccessMask(6, 23, 109, 0, 2)
player.packetDispatch.sendAccessMask(6, 28, 109, 0, 2)
player.packetDispatch.sendAccessMask(6, 36, 109, 0, 2)
player.packetDispatch.sendAccessMask(6, 44, 109, 0, 2)
player.packetDispatch.sendAccessMask(6, 52, 109, 0, 2)
player.packetDispatch.sendIfaceSettings(6, 18, 109, 0, 2)
player.packetDispatch.sendIfaceSettings(6, 23, 109, 0, 2)
player.packetDispatch.sendIfaceSettings(6, 28, 109, 0, 2)
player.packetDispatch.sendIfaceSettings(6, 36, 109, 0, 2)
player.packetDispatch.sendIfaceSettings(6, 44, 109, 0, 2)
player.packetDispatch.sendIfaceSettings(6, 52, 109, 0, 2)
visualizeRecords()
}
@@ -8,11 +8,11 @@ import rs09.game.world.GameWorld
* Handles the corporeal beast warning interface
* @author Ceikry
*/
class CorporealBeastWarningInterface : InterfaceListener(){
class CorporealBeastWarningInterface : InterfaceListener{
val COMPONENT_ID = 650
override fun defineListeners() {
override fun defineInterfaceListeners() {
on(COMPONENT_ID,17){player,component,_,_,_,_ ->
if(player.getAttribute("corp-beast-cave-delay",0) <= GameWorld.ticks) {
player.properties.teleportLocation = player.location.transform(4, 0, 0).also { close(player,component) }
@@ -1,17 +1,21 @@
package rs09.game.interaction
import api.ContentInterface
import core.game.node.Node
import core.game.node.entity.Entity
import core.game.node.entity.player.Player
import core.game.world.map.Location
abstract class InteractionListener : Listener{
companion object {
val ITEM = 0
val SCENERY = 1
val NPC = 2
val GROUNDITEM = 3
}
interface InteractionListener : ContentInterface{
val ITEM: Int
get() = 0
val SCENERY: Int
get() = 1
val NPC: Int
get() = 2
val GROUNDITEM: Int
get() = 3
fun on(id: Int, type: Int, vararg option: String,handler: (player: Player, node: Node) -> Boolean){
InteractionListeners.add(id,type,option,handler)
}
@@ -65,4 +69,14 @@ abstract class InteractionListener : Listener{
fun onDig(location: Location,method: (player: Player) -> Unit){
SpadeDigListener.registerListener(location,method)
}
fun defineListeners()
companion object
{
val ITEM = 0
val SCENERY = 1
val NPC = 2
val GROUNDITEM = 3
}
}
@@ -1,9 +1,17 @@
package rs09.game.interaction
import api.ContentInterface
import core.game.component.Component
import core.game.node.entity.player.Player
abstract class InterfaceListener : Listener {
/**
* An interface for writing content that allows the class to handle game interface interactions
*
* Interactions should be defined in the required [defineInterfaceListeners] method.
*/
interface InterfaceListener : ContentInterface {
fun defineInterfaceListeners()
fun on(componentID: Int, buttonID: Int, handler: (player: Player, component: Component, opcode: Int, buttonID: Int, slot: Int, itemID: Int) -> Boolean){
InterfaceListeners.add(componentID,buttonID,handler)
}
@@ -3,8 +3,9 @@ package rs09.game.interaction
import api.StartupListener
interface Listener : StartupListener {
fun defineListeners()
override fun startup() {
defineListeners()
}
fun defineListeners()
}
@@ -7,12 +7,12 @@ import org.rs09.consts.Components
import org.rs09.consts.Items
import rs09.game.interaction.InterfaceListener
class CreditShopInterface : InterfaceListener() {
class CreditShopInterface : InterfaceListener {
val CREDIT_SHOP = Components.CREDIT_SHOP
val TEXT_CHILD = 39
override fun defineListeners() {
override fun defineInterfaceListeners() {
on(CREDIT_SHOP){player, component, opcode, buttonID, slot, itemID ->
val item = getItem(buttonID)
@@ -21,12 +21,12 @@ val RING_3 = arrayOf('p','s','r','q')
* Handles the fairy ring interface
* @author Ceikry
*/
class FairyRingInterface : InterfaceListener(){
class FairyRingInterface : InterfaceListener{
val RINGS = 734
val TRAVEL_LOG = 735
override fun defineListeners() {
override fun defineInterfaceListeners() {
onOpen(RINGS){player, _ ->
player.interfaceManager.openSingleTab(Component(TRAVEL_LOG))
@@ -11,14 +11,14 @@ import org.rs09.consts.Components
import rs09.game.interaction.InterfaceListener
import rs09.game.world.GameWorld.settings
class MainGameInterface : InterfaceListener() {
class MainGameInterface : InterfaceListener {
val TOPLEVEL = Components.TOPLEVEL_548
val TOPLEVEL_FS = Components.TOPLEVEL_FULLSCREEN_746
val RUN_BUTTON = Components.TOPSTAT_RUN_750
val FILTER_BUTTONS = Components.FILTERBUTTONS_751
val REPORT_ABUSE = Components.SNAPSHOT_MAIN_553
override fun defineListeners() {
override fun defineInterfaceListeners() {
on(FILTER_BUTTONS){player, _, _, buttonID, _, _ ->
if(buttonID == 27)
openReport(player)
@@ -102,6 +102,7 @@ class MainGameInterface : InterfaceListener() {
player.packetDispatch.sendMessage("You can't do this right now.")
return
}
player.interfaceManager.close()
player.interfaceManager.openWindowsPane(Component(755))
val posHash = player.location.z shl 28 or (player.location.x shl 14) or player.location.y
player.packetDispatch.sendScriptConfigs(622, posHash, "", 0)
@@ -7,7 +7,7 @@ import org.rs09.consts.NPCs
import rs09.game.content.dialogue.DialogueFile
import rs09.game.interaction.InterfaceListener
class NPCContactInterface : InterfaceListener() {
class NPCContactInterface : InterfaceListener {
val contactNPCs = arrayOf(NPCs.HONEST_JIMMY_4362, NPCs.BERT_3108, NPCs.ADVISOR_GHRIM_1375, NPCs.TURAEL_8273, NPCs.LANTHUS_1526, NPCs.SUMONA_7780, NPCs.MAZCHNA_8274, NPCs.DURADEL_8275, NPCs.VANNAKA_1597, NPCs.DARK_MAGE_2262, NPCs.CHAELDAR_1598, NPCs.CYRISUS_432, NPCs.LARRY_5424)
val DialogueFiles = arrayOf<DialogueFile?>(
/*TODO("Honest Jimmy"),
@@ -25,7 +25,7 @@ class NPCContactInterface : InterfaceListener() {
TODO("Larry")*/
)
val INTER = 429
override fun defineListeners() {
override fun defineInterfaceListeners() {
//Remove a bunch of the buttons/heads so that people don't
//waste runes on spells that aren't implemented
@@ -46,9 +46,9 @@ object RulesAndInfo {
}
}
class RulesListener : InterfaceListener()
class RulesListener : InterfaceListener
{
override fun defineListeners() {
override fun defineInterfaceListeners() {
onClose(384){player, _ ->
if(!getAttribute(player, "rules:confirmed", false))
runTask(player, 1) { RulesAndInfo.openFor(player); sendDialogue(player, "Please read the rules.") }
@@ -11,12 +11,12 @@ import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
import rs09.game.interaction.InterfaceListener
class SilverInterface : InterfaceListener() {
class SilverInterface : InterfaceListener {
val IFACE = Components.CRAFTING_SILVER_CASTING_438
val ANIM = getAnimation(899)
override fun defineListeners() {
override fun defineInterfaceListeners() {
onOpen(IFACE){player, _ ->
sendItemOnInterface(player, IFACE, 17, 1718)
sendItemOnInterface(player, IFACE, 24, 1724)
@@ -89,7 +89,7 @@ class SilverInterface : InterfaceListener() {
}
}
class SilverBarUseWith : InteractionListener() {
class SilverBarUseWith : InteractionListener {
val FURNACES = intArrayOf(2966, 3044, 3294, 4304, 6189, 11009, 11010, 11666, 12100, 12809, 18497, 18525, 18526, 21879, 22721, 26814, 28433, 28434, 30021, 30510, 36956, 37651)
override fun defineListeners() {
onUseWith(SCENERY, Items.SILVER_BAR_2355, *FURNACES){ player, _, _ ->
@@ -6,10 +6,10 @@ import core.game.world.update.flag.context.Graphics
import org.rs09.consts.Components
import rs09.game.interaction.InterfaceListener
class TeleotherInterface : InterfaceListener() {
class TeleotherInterface : InterfaceListener {
val IFACE = Components.TELEPORT_OTHER_326
override fun defineListeners() {
override fun defineInterfaceListeners() {
on(IFACE){player, _, _, button, _, _ ->
if(button == 5){
player.lock(2)
@@ -13,8 +13,8 @@ import core.game.node.entity.player.Player
import org.rs09.consts.Components
import rs09.game.interaction.InterfaceListener
class ExchangeItemSets : InterfaceListener() {
override fun defineListeners() {
class ExchangeItemSets : InterfaceListener {
override fun defineInterfaceListeners() {
onOpen(Components.EXCHANGE_ITEMSETS_645) { player, _ ->
val listener: InventoryListener
setAttribute(player, "ge-listener", InventoryListener(player).also { listener = it })
@@ -25,12 +25,12 @@ import rs09.game.system.SystemLogger
* Handles the grand exchange interface (Stock Market)
* @author Ceikry
*/
class StockMarket : InterfaceListener() {
override fun defineListeners() {
class StockMarket : InterfaceListener {
override fun defineInterfaceListeners() {
onOpen(Components.STOCKMARKET_105){player, _ ->
player.packetDispatch.sendInterfaceConfig(105, 193, true)
player.packetDispatch.sendAccessMask(6, 211, 105, -1, -1)
player.packetDispatch.sendAccessMask(6, 209, 105, -1, -1)
player.packetDispatch.sendIfaceSettings(6, 211, 105, -1, -1)
player.packetDispatch.sendIfaceSettings(6, 209, 105, -1, -1)
player.varpManager.get(1112).setVarbit(0, -1).send(player)
return@onOpen true
}
@@ -8,7 +8,7 @@ import rs09.game.interaction.InteractionListener
* Handles the bracelet of clay operate option.
* @author Ceikry
*/
class BraceletOfClayPlugin : InteractionListener() {
class BraceletOfClayPlugin : InteractionListener {
override fun defineListeners() {
@@ -8,7 +8,7 @@ import rs09.game.interaction.InteractionListener
* @author bushtail
*/
class CadavaPotionListener : InteractionListener() {
class CadavaPotionListener : InteractionListener {
var POTION = Items.CADAVA_POTION_756
@@ -4,7 +4,7 @@ import api.*
import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
class DrinkBlamishOilListener : InteractionListener() {
class DrinkBlamishOilListener : InteractionListener {
override fun defineListeners() {
on(Items.BLAMISH_OIL_1582, ITEM, "drink"){player, _ ->
@@ -9,7 +9,7 @@ import java.util.*
* Listener for enchanted jewellery options
* @author Ceikry
*/
class EnchantedJewelleryListener : InteractionListener() {
class EnchantedJewelleryListener : InteractionListener {
val IDs: IntArray
init {
@@ -9,7 +9,7 @@ import rs09.game.content.dialogue.DialogueFile
import rs09.game.interaction.InteractionListener
import rs09.tools.END_DIALOGUE
class GodBookListeners : InteractionListener() {
class GodBookListeners : InteractionListener {
val GB_SARADOMIN = Items.HOLY_BOOK_3840
val GB_ZAMORAK = Items.UNHOLY_BOOK_3842
@@ -30,7 +30,7 @@ private const val LAUNCH_ANIMATION = 4547
* Handles the grand tree pod options
* @author Ceikry
*/
class GrandSeedPodHandler : InteractionListener() {
class GrandSeedPodHandler : InteractionListener {
override fun defineListeners() {
on(Items.GRAND_SEED_POD_9469, ITEM, "squash", "launch"){player, _ ->
@@ -9,7 +9,7 @@ import rs09.game.content.global.WeightedItem
import rs09.game.interaction.InteractionListener
import rs09.game.system.SystemLogger
class ImplingJarListener : InteractionListener() {
class ImplingJarListener : InteractionListener {
val JARS = ImplingLoot.values().map { it.jarId }.toIntArray()
@@ -6,7 +6,7 @@ import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
import rs09.game.interaction.InteractionListeners.run
class ItemQuestRequirementListener : InteractionListener() {
class ItemQuestRequirementListener : InteractionListener {
private val fremennikIslesEquipment = intArrayOf(Items.HELM_OF_NEITIZNOT_10828, Items.HELM_OF_NEITIZNOT_E_12680, Items.HELM_OF_NEITIZNOT_CHARGED_12681)
private val fremennikTrialsEquipment = intArrayOf(Items.BERSERKER_HELM_3751, Items.BERSERKER_HELM_13408, Items.BERSERKER_HELM_E_12674, Items.BERSERKER_HELM_CHARGED_12675,
@@ -11,7 +11,7 @@ import rs09.tools.END_DIALOGUE
/**
* Handles the Staff of the Raven's (2021 Hween Reward) Recolor Transformation
*/
class StaffOfTheRaven : InteractionListener() {
class StaffOfTheRaven : InteractionListener {
val ids = intArrayOf(14654, 14655, 14656)
override fun defineListeners() {
on(ids, ITEM, "recolor", "operate"){player, node ->
@@ -1,94 +0,0 @@
package rs09.game.interaction.item
import api.*
import core.game.node.entity.player.Player
import core.game.node.entity.player.link.TeleportManager
import core.game.node.entity.skill.Skills
import core.game.world.map.Location
import org.json.simple.JSONObject
import org.rs09.consts.Items
import rs09.ServerStore
import rs09.ServerStore.Companion.getBoolean
import rs09.game.content.dialogue.DialogueFile
import rs09.game.content.global.worldevents.WorldEvents
import rs09.game.content.global.worldevents.shootingstar.ShootingStar
import rs09.game.content.global.worldevents.shootingstar.ShootingStarEvent
import rs09.game.interaction.InteractionListener
import java.util.concurrent.TimeUnit
class StarRingListener : InteractionListener(){
val RING = Items.RING_OF_THE_STAR_SPRITE_14652
override fun defineListeners() {
on(RING, ITEM, "rub", "operate"){player, node ->
val star = WorldEvents.get("shooting-stars") as? ShootingStarEvent
if(star == null) sendDialogue(player, "There is currently no active star.").also { return@on true }
if(getStoreFile().getBoolean(player.username.toLowerCase())){
sendDialogue(player, "The ring is still recharging.")
return@on true
}
val condition: (Player) -> Boolean = when(star?.star!!.location.toLowerCase()){
"canifis bank" -> { p -> p.questRepository.isComplete("Priest in Peril")}
"crafting guild" -> {p -> hasLevelStat(p, Skills.CRAFTING, 40) }
"south crandor mining site" -> {p -> p.questRepository.isComplete("Dragon Slayer")}
else -> {_ -> true}
}
if(!condition.invoke(player) || player.skullManager.isWilderness){
sendDialogue(player, "Magical forces prevent your teleportation.")
return@on true
}
val shouldWarn = when(star.star.location){
"North Edgeville mining site",
"Southern wilderness mine",
"Pirates' Hideout mine",
"Lava Maze mining site",
"Mage Arena bank" -> true
else -> false
}
openDialogue(player, RingDialogue(shouldWarn, star.star))
return@on true
}
}
internal class RingDialogue(val shouldWarn: Boolean, val star: ShootingStar) : DialogueFile(){
override fun handle(componentID: Int, buttonID: Int) {
if(shouldWarn){
when(stage) {
0 -> dialogue("WARNING: That mining site is located in the wilderness.").also { stage++ }
1 -> player!!.dialogueInterpreter.sendOptions("Continue?","Yes","No").also { stage++ }
2 -> when(buttonID){
1 -> teleport(player!!, star).also { end() }
2 -> end()
}
}
} else {
when(stage){
0 -> player!!.dialogueInterpreter.sendOptions("Teleport to the Star?", "Yes", "No").also { stage++ }
1 -> when(buttonID){
1 -> teleport(player!!, star).also { end() }
2 -> end()
}
}
}
}
fun teleport(player: Player, star: ShootingStar){
teleport(player, star.crash_locations[star.location]!!.transform(0, -1, 0), TeleportManager.TeleportType.MINIGAME)
getStoreFile()[player.username.toLowerCase()] = true
}
}
companion object {
fun getStoreFile(): JSONObject {
return ServerStore.getArchive("daily-star-ring")
}
}
}
@@ -11,7 +11,7 @@ import rs09.game.interaction.InteractionListener
* Interaction listener for the Toy Horsey item
* @author Woah
*/
class ToyHorseListener : InteractionListener() {
class ToyHorseListener : InteractionListener {
// Map of horse item ids to their correct emote
val HORSEY_MAP = mapOf(
@@ -6,7 +6,7 @@ import core.game.world.update.flag.context.Graphics
import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
class ToyListeners : InteractionListener() {
class ToyListeners : InteractionListener {
companion object {
val MARIONETTES = intArrayOf(Items.RED_MARIONETTE_6867, Items.GREEN_MARIONETTE_6866, Items.BLUE_MARIONETTE_6865)
private val MARIONETTE_JUMP = Animation(3003)
@@ -5,7 +5,7 @@ import api.*
import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
class HaySackOnSpear : InteractionListener() {
class HaySackOnSpear : InteractionListener {
val HAYSACK = Items.HAY_SACK_6057
val SPEAR = Items.BRONZE_SPEAR_1237
@@ -6,7 +6,7 @@ import core.game.world.update.flag.context.Animation
import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
class OilFishingRodListener : InteractionListener() {
class OilFishingRodListener : InteractionListener {
override fun defineListeners() {
onUseWith(ITEM, Items.BLAMISH_OIL_1582, Items.FISHING_ROD_307) {player, used, with ->
player.pulseManager.run(object : Pulse() {
@@ -7,7 +7,7 @@ import rs09.game.interaction.InteractionListener
import kotlin.collections.toIntArray
import kotlin.math.min
class PoisonedWeaponListeners : InteractionListener() {
class PoisonedWeaponListeners : InteractionListener {
override fun defineListeners() {
val poisons = intArrayOf(Items.WEAPON_POISON_187, Items.WEAPON_POISON_PLUS_5937, Items.WEAPON_POISON_PLUS_PLUS_5940)
val poisonableItems = PoisonSets.itemMap.keys.toIntArray()
@@ -7,7 +7,7 @@ import rs09.game.content.dialogue.DialogueFile
import rs09.game.interaction.InteractionListener
import rs09.tools.END_DIALOGUE
class TOTTHelmOnCape : InteractionListener() {
class TOTTHelmOnCape : InteractionListener {
override fun defineListeners() {
onUseWith(ITEM, Items.SLAYER_HELMET_13263, Items.SLAYER_CAPE_9786, Items.SLAYER_CAPET_9787){ player, used, with ->
val alreadyHasHelm = getAttribute(player, "cape_perks:tott:helmet-stored", false)
@@ -6,7 +6,7 @@ import core.game.node.entity.skill.Skills
import org.rs09.consts.Items
import rs09.game.interaction.InteractionListener
class WatermelonOnSack : InteractionListener() {
class WatermelonOnSack : InteractionListener {
val SACK = Items.HAY_SACK_6058
val WATERMELON = Items.WATERMELON_5982
@@ -7,7 +7,7 @@ import org.rs09.consts.NPCs
import rs09.game.content.dialogue.region.examcentre.ArchaeologistcalExpertUsedOnDialogueFile
import rs09.game.interaction.InteractionListener
open class ArchaeologicalExpertListener() : InteractionListener() {
open class ArchaeologicalExpertListener() : InteractionListener {
val staff = Items.ANCIENT_STAFF_4675
val unidentifiedLiquid = Items.UNIDENTIFIED_LIQUID_702
val nitroglycerin = Items.NITROGLYCERIN_703

Some files were not shown because too many files have changed in this diff Show More