diff --git a/Server/src/main/content/global/ame/RandomEventManager.kt b/Server/src/main/content/global/ame/RandomEventManager.kt deleted file mode 100644 index 5054b110d..000000000 --- a/Server/src/main/content/global/ame/RandomEventManager.kt +++ /dev/null @@ -1,94 +0,0 @@ -package content.global.ame - -import core.api.* -import core.game.event.EventHook -import core.game.event.TickEvent -import core.game.node.entity.Entity -import core.game.node.entity.player.Player -import core.game.world.map.zone.ZoneRestriction -import core.tools.RandomFunction -import core.tools.SystemLogger -import core.game.system.command.Privilege -import core.game.world.GameWorld -import core.game.world.repository.Repository -import core.tools.Log -import kotlin.random.Random -import content.global.ame.RandomEvents - -class RandomEventManager(val player: Player? = null) : LoginListener, EventHook, Commands { - var event: content.global.ame.RandomEventNPC? = null - var enabled: Boolean = false - var nextSpawn = 0 - val skills = arrayOf("WoodcuttingSkillPulse","FishingPulse","MiningSkillPulse","BoneBuryingOptionPlugin") - - override fun login(player: Player) { - if(player.isArtificial) return - val instance = RandomEventManager(player) - player.hook(Event.Tick, instance) - setAttribute(player, "random-manager", instance) - instance.rollNextSpawn() - instance.enabled = true - } - - override fun process(entity: Entity, event: TickEvent) { - if (GameWorld.ticks > nextSpawn && getAttribute(player!!, "tutorial:complete", false)) fireEvent() - } - - fun fireEvent() { - if(!enabled){ - rollNextSpawn() - return - } - if (player!!.zoneMonitor.isRestricted(ZoneRestriction.RANDOM_EVENTS)) { - rollNextSpawn() - return - } - if (getAttribute(player, "re-npc", null) != null) return - val currentAction = player.pulseManager.current?.toString() ?: "None" - - //Give us a decent pool of events to pick from randomly, with the skill-based random event mixed in if applicable. - //This is just to provide a bit more randomization (you don't want to get the skill-based randoms EVERY TIME when training woodcutting, for example.) - val ame = listOf ( - RandomEvents.getSkillBasedRandomEvent (player.skills.lastTrainedSkill), - RandomEvents.getNonSkillRandom(), - RandomEvents.getNonSkillRandom() - ).filter{ it != null }.random() ?: RandomEvents.getNonSkillRandom() //Absolute cosmic bit-flip tier safety fallback - - event = ame.npc.create(player,ame.loot,ame.type) - if (event!!.spawnLocation == null) { - nextSpawn = GameWorld.ticks + 3000 - log(this::class.java, Log.WARN, "Tried to spawn random event for ${player.username} but spawn location was null!") - return - } - event!!.init() - rollNextSpawn() - log(this::class.java, Log.FINE, "Fired ${event!!.name} for ${player.username}") - } - - private fun rollNextSpawn() { - nextSpawn = GameWorld.ticks + RandomFunction.random(MIN_DELAY_TICKS, MAX_DELAY_TICKS) - } - - override fun defineCommands() { - define("targeted-ame", Privilege.ADMIN, "targeted-ame username", "Summons a random for the given user") {player, args -> - val username = args[1] - val target = Repository.getPlayerByName(username) - - if (target == null) - reject(player, "Unable to find player $username!") - - getInstance(target!!)?.fireEvent() - } - } - - companion object { - var AVG_DELAY_TICKS = 6000 // 60 minutes - var MIN_DELAY_TICKS = AVG_DELAY_TICKS / 2 - var MAX_DELAY_TICKS = MIN_DELAY_TICKS + AVG_DELAY_TICKS // window of 60 min centered on 60 min (30 to 90 min) - - @JvmStatic fun getInstance(player: Player): RandomEventManager? - { - return getAttribute(player, "random-manager", null) - } - } -} diff --git a/Server/src/main/content/global/ame/RandomEventNPC.kt b/Server/src/main/content/global/ame/RandomEventNPC.kt index 4503948f5..cbcf794ce 100644 --- a/Server/src/main/content/global/ame/RandomEventNPC.kt +++ b/Server/src/main/content/global/ame/RandomEventNPC.kt @@ -39,7 +39,6 @@ abstract class RandomEventNPC(id: Int) : NPC(id) { open fun terminate() { finalized = true pulseManager.clear(PulseType.STANDARD) - content.global.ame.RandomEventManager.getInstance(player)?.event = null if (initialized) { poofClear(this) } diff --git a/Server/src/main/content/global/ame/RandomEvents.kt b/Server/src/main/content/global/ame/RandomEvents.kt index 32212d157..ec7066858 100644 --- a/Server/src/main/content/global/ame/RandomEvents.kt +++ b/Server/src/main/content/global/ame/RandomEvents.kt @@ -19,7 +19,7 @@ import core.api.utils.WeightBasedTable import core.api.utils.WeightedItem import core.game.node.entity.skill.Skills -enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = null, val skillId: Int = -1, val type: String = "") { +enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = null, val skillIds: IntArray = intArrayOf(), val type: String = "") { SANDWICH_LADY(npc = SandwichLadyRENPC()), GENIE(npc = GenieNPC()), CERTER(npc = CerterNPC(), loot = WeightBasedTable.create( @@ -41,14 +41,14 @@ enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = n )), DRILL_DEMON(npc = SeargentDamienNPC()), EVIL_CHICKEN(npc = EvilChickenNPC()), - EVIL_BOB(npc = EvilBobNPC(), skillId = Skills.FISHING), + EVIL_BOB(npc = EvilBobNPC(), skillIds = intArrayOf(Skills.FISHING, Skills.MAGIC)), SURPRISE_EXAM(npc = MysteriousOldManNPC(), type = "sexam"), - FREAKY_FORESTER(npc = FreakyForesterNPC(), skillId = Skills.WOODCUTTING), - TREE_SPIRIT(npc = TreeSpiritRENPC(), skillId = Skills.WOODCUTTING), - RIVER_TROLL(RiverTrollRENPC(), skillId = Skills.FISHING), - ROCK_GOLEM(RockGolemRENPC(), skillId = Skills.MINING), - SHADE(ShadeRENPC(), skillId = Skills.PRAYER), - ZOMBIE(ZombieRENPC(), skillId = Skills.PRAYER); + FREAKY_FORESTER(npc = FreakyForesterNPC(), skillIds = intArrayOf(Skills.WOODCUTTING)), + TREE_SPIRIT(npc = TreeSpiritRENPC(), skillIds = intArrayOf(Skills.WOODCUTTING)), + RIVER_TROLL(RiverTrollRENPC(), skillIds = intArrayOf(Skills.FISHING)), + ROCK_GOLEM(RockGolemRENPC(), skillIds = intArrayOf(Skills.MINING)), + SHADE(ShadeRENPC(), skillIds = intArrayOf(Skills.PRAYER)), + ZOMBIE(ZombieRENPC(), skillIds = intArrayOf(Skills.PRAYER)); companion object { @JvmField @@ -68,11 +68,12 @@ enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = n private fun populateMappings() { for (event in values()) { - if (event.skillId != -1) { - val list = skillMap[event.skillId] ?: ArrayList().also { skillMap[event.skillId] = it } + for (id in event.skillIds) { + val list = skillMap[id] ?: ArrayList().also { skillMap[id] = it } list.add (event) } - else nonSkillList.add (event) + if (event.skillIds.isEmpty()) + nonSkillList.add (event) } } } diff --git a/Server/src/main/content/global/ame/events/MysteriousOldManDialogue.kt b/Server/src/main/content/global/ame/events/MysteriousOldManDialogue.kt index 5366ee17b..628a3c8f0 100644 --- a/Server/src/main/content/global/ame/events/MysteriousOldManDialogue.kt +++ b/Server/src/main/content/global/ame/events/MysteriousOldManDialogue.kt @@ -3,6 +3,7 @@ package content.global.ame.events import core.game.node.entity.player.Player import content.global.ame.events.supriseexam.SurpriseExamUtils import core.game.dialogue.DialogueFile +import core.game.system.timer.impl.AntiMacro class MysteriousOldManDialogue(val type: String) : DialogueFile() { @@ -22,11 +23,11 @@ class MysteriousOldManDialogue(val type: String) : DialogueFile() { 1 -> { end() teleport(player!!,type) - content.global.ame.RandomEventManager.getInstance(player!!)?.event?.terminate() + AntiMacro.terminateEventNpc(player!!) } 2 -> { end() - content.global.ame.RandomEventManager.getInstance(player!!)?.event?.terminate() + AntiMacro.terminateEventNpc(player!!) } } } diff --git a/Server/src/main/content/global/ame/events/certer/CerterDialogue.kt b/Server/src/main/content/global/ame/events/certer/CerterDialogue.kt index 25ac73fdf..27f72a317 100644 --- a/Server/src/main/content/global/ame/events/certer/CerterDialogue.kt +++ b/Server/src/main/content/global/ame/events/certer/CerterDialogue.kt @@ -5,6 +5,7 @@ import core.game.component.Component import core.game.node.entity.impl.PulseType import core.game.node.entity.player.link.emote.Emotes import core.game.dialogue.DialogueFile +import core.game.system.timer.impl.AntiMacro import core.tools.END_DIALOGUE class CerterDialogue(val initial: Boolean) : DialogueFile() { @@ -30,13 +31,13 @@ class CerterDialogue(val initial: Boolean) : DialogueFile() { npc("Sorry, I don't think so.").also { player!!.setAttribute("certer:reward", true) stage = END_DIALOGUE - content.global.ame.RandomEventManager.getInstance(player!!)?.event?.terminate() + AntiMacro.terminateEventNpc(player!!) } } else { npc("Thank you, I hope you like your present. I must be", "leaving now though.").also { player!!.setAttribute("certer:reward", true) stage = END_DIALOGUE - content.global.ame.RandomEventManager.getInstance(player!!)!!.event!!.loot!!.roll().forEach { addItemOrDrop(player!!, it.id, it.amount) } + AntiMacro.rollEventLoot(player!!).forEach { addItemOrDrop(player!!, it.id, it.amount) } } } } @@ -51,7 +52,7 @@ class CerterDialogue(val initial: Boolean) : DialogueFile() { // Wave goodbye npc!!.animate(Emotes.WAVE.animation) // Terminate the event - content.global.ame.RandomEventManager.getInstance(player!!)?.event?.terminate() + AntiMacro.terminateEventNpc(player!!) } else { player!!.setAttribute("random:pause", false) } diff --git a/Server/src/main/content/global/ame/events/certer/CerterEventInterface.kt b/Server/src/main/content/global/ame/events/certer/CerterEventInterface.kt index e64cf3403..96d8f313e 100644 --- a/Server/src/main/content/global/ame/events/certer/CerterEventInterface.kt +++ b/Server/src/main/content/global/ame/events/certer/CerterEventInterface.kt @@ -1,10 +1,10 @@ package content.global.ame.events.certer -import content.global.ame.RandomEventManager import core.game.node.entity.player.Player import core.game.node.item.Item import org.rs09.consts.Items import core.game.interaction.InterfaceListener +import core.game.system.timer.impl.AntiMacro class CerterEventInterface : InterfaceListener { val CERTER_INTERFACE = 184 @@ -37,7 +37,7 @@ class CerterEventInterface : InterfaceListener { val correctAnswer = player.getAttribute("certer:correctIndex", 0) player.setAttribute("certer:correct", correctAnswer == answer) player.interfaceManager.close() - player.dialogueInterpreter.open(CerterDialogue(false), RandomEventManager.getInstance(player)!!.event?.asNpc()) + player.dialogueInterpreter.open(CerterDialogue(false), AntiMacro.getEventNpc(player)) return@on true } diff --git a/Server/src/main/content/global/ame/events/drilldemon/SeargentDamienDialogue.kt b/Server/src/main/content/global/ame/events/drilldemon/SeargentDamienDialogue.kt index 6ab770f21..93e529fbf 100644 --- a/Server/src/main/content/global/ame/events/drilldemon/SeargentDamienDialogue.kt +++ b/Server/src/main/content/global/ame/events/drilldemon/SeargentDamienDialogue.kt @@ -1,15 +1,15 @@ package content.global.ame.events.drilldemon -import core.game.dialogue.FacialExpression import core.game.system.task.Pulse import core.game.dialogue.DialogueFile +import core.game.system.timer.impl.AntiMacro import core.tools.END_DIALOGUE import core.tools.START_DIALOGUE class SeargentDamienDialogue(val isCorrect: Boolean = false) : DialogueFile() { override fun handle(componentID: Int, buttonID: Int) { var correctAmt = player!!.getAttribute(DrillDemonUtils.DD_CORRECT_COUNTER,0) - if(correctAmt == 4 && content.global.ame.RandomEventManager.getInstance(player!!)!!.event == null) { + if(correctAmt == 4 && AntiMacro.getEventNpc(player!!) == null) { when(stage){ 0 -> npc(core.game.dialogue.FacialExpression.OLD_NORMAL,"My god you actually did it, you limp","wristed worm-bodied MAGGOT! Take this","and get out of my sight.").also { stage++ } 1 -> { @@ -24,7 +24,7 @@ class SeargentDamienDialogue(val isCorrect: Boolean = false) : DialogueFile() { }) } } - } else if(content.global.ame.RandomEventManager.getInstance(player!!)!!.event == null){ + } else if(AntiMacro.getEventNpc(player!!) == null){ when(stage){ START_DIALOGUE -> if(isCorrect) npc(core.game.dialogue.FacialExpression.OLD_NORMAL,"Good! Now...").also { stage++ } else npc(core.game.dialogue.FacialExpression.OLD_ANGRY1,"WRONG, MAGGOT!").also { stage++ } 1 -> { @@ -40,12 +40,12 @@ class SeargentDamienDialogue(val isCorrect: Boolean = false) : DialogueFile() { 1 -> { end() DrillDemonUtils.teleport(player!!) - content.global.ame.RandomEventManager.getInstance(player!!)!!.event?.terminate() + AntiMacro.terminateEventNpc(player!!) stage = END_DIALOGUE } 2 -> { end() - content.global.ame.RandomEventManager.getInstance(player!!)!!.event?.terminate() + AntiMacro.terminateEventNpc(player!!) stage = END_DIALOGUE } } diff --git a/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt b/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt index 3f6d8cca3..f8e778f96 100644 --- a/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt +++ b/Server/src/main/content/global/ame/events/evilbob/EvilBobNPC.kt @@ -4,6 +4,7 @@ import content.global.ame.RandomEventNPC import core.api.* import core.api.utils.WeightBasedTable import core.game.node.entity.npc.NPC +import core.game.system.timer.impl.AntiMacro import org.rs09.consts.NPCs import org.rs09.consts.Sounds @@ -24,7 +25,7 @@ class EvilBobNPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(N EvilBobUtils.teleport(player) resetAnimator(player) openDialogue(player, EvilBobDialogue(), NPCs.EVIL_BOB_2479) - content.global.ame.RandomEventManager.getInstance(player)?.event?.terminate() + AntiMacro.terminateEventNpc(player) } } } diff --git a/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt b/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt index 4932298f7..5661c0d7c 100644 --- a/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt +++ b/Server/src/main/content/global/ame/events/freakyforester/FreakyForesterNPC.kt @@ -1,12 +1,12 @@ package content.global.ame.events.freakyforester -import content.global.ame.RandomEventManager import content.global.ame.RandomEventNPC import core.api.* import org.rs09.consts.NPCs import core.api.utils.WeightBasedTable import core.game.node.entity.npc.NPC import core.game.system.task.Pulse +import core.game.system.timer.impl.AntiMacro import core.game.world.update.flag.context.Graphics import org.rs09.consts.Sounds @@ -28,7 +28,7 @@ class FreakyForesterNPC(override var loot: WeightBasedTable? = null) : RandomEve 7 -> { FreakUtils.teleport(player) FreakUtils.giveFreakTask(player) - RandomEventManager.getInstance(player)!!.event?.terminate() + AntiMacro.terminateEventNpc(player) openDialogue(player, FreakyForesterDialogue(), FreakUtils.freakNpc) resetAnimator(player) } diff --git a/Server/src/main/content/global/ame/events/genie/GenieDialogue.kt b/Server/src/main/content/global/ame/events/genie/GenieDialogue.kt index 829e0ecc4..b650e8e22 100644 --- a/Server/src/main/content/global/ame/events/genie/GenieDialogue.kt +++ b/Server/src/main/content/global/ame/events/genie/GenieDialogue.kt @@ -3,6 +3,7 @@ package content.global.ame.events.genie import core.api.* import core.game.dialogue.FacialExpression import core.game.dialogue.DialogueFile +import core.game.system.timer.impl.AntiMacro import core.tools.END_DIALOGUE class GenieDialogue : DialogueFile() { @@ -10,7 +11,7 @@ class GenieDialogue : DialogueFile() { val assigned = player!!.getAttribute("genie:item",0) npcl(core.game.dialogue.FacialExpression.NEUTRAL, "Ah, so you are there, ${player!!.name.capitalize()}. I'm so glad you summoned me. Please take this lamp and make your wish.") addItemOrDrop(player!!, assigned) - content.global.ame.RandomEventManager.getInstance(player!!)!!.event?.terminate() + AntiMacro.terminateEventNpc(player!!) stage = END_DIALOGUE } } \ No newline at end of file diff --git a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyDialogue.kt b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyDialogue.kt index 7cfc456ea..8374daf41 100644 --- a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyDialogue.kt +++ b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyDialogue.kt @@ -6,6 +6,7 @@ import core.game.node.entity.combat.ImpactHandler import core.game.node.item.GroundItemManager import core.game.node.item.Item import core.game.dialogue.DialogueFile +import core.game.system.timer.impl.AntiMacro import core.tools.END_DIALOGUE class SandwichLadyDialogue(val isChoice: Boolean) : DialogueFile() { @@ -26,13 +27,13 @@ class SandwichLadyDialogue(val isChoice: Boolean) : DialogueFile() { 0 -> if(choice != assigned){ npc!!.sendChat("That's not what I said you could have!") player!!.impactHandler.manualHit(npc,3,ImpactHandler.HitsplatType.NORMAL) - content.global.ame.RandomEventManager.getInstance(player!!)!!.event?.terminate() + AntiMacro.terminateEventNpc(player!!) } else { npc("Here you are, dear. I hope you enjoy it!") if(!player!!.inventory.add(Item(assigned))){ GroundItemManager.create(Item(assigned),player) } - content.global.ame.RandomEventManager.getInstance(player!!)!!.event?.terminate() + AntiMacro.terminateEventNpc(player!!) stage = END_DIALOGUE } } diff --git a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyInterface.kt b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyInterface.kt index 306dc6a84..c0d4773bd 100644 --- a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyInterface.kt +++ b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyInterface.kt @@ -3,7 +3,7 @@ package content.global.ame.events.sandwichlady import core.game.node.item.Item import org.rs09.consts.Items import core.game.interaction.InterfaceListener -import content.global.ame.RandomEventManager +import core.game.system.timer.impl.AntiMacro class SandwichLadyInterface : InterfaceListener { @@ -18,7 +18,7 @@ class SandwichLadyInterface : InterfaceListener { override fun defineInterfaceListeners() { on(SANDWICH_INTERFACE){player, _, _, buttonID, _, _ -> - val event = RandomEventManager.getInstance(player)!!.event + val event = AntiMacro.getEventNpc(player) if (event == null) { player.interfaceManager.close() return@on true diff --git a/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt b/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt index b5e3b3090..1bc33d344 100644 --- a/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt +++ b/Server/src/main/content/global/handlers/npc/NPCTalkListener.kt @@ -7,6 +7,7 @@ import content.global.ame.RandomEvents import content.minigame.gnomecooking.* import core.game.interaction.InteractionListener import core.game.interaction.IntType +import core.game.system.timer.impl.AntiMacro /** * Handles the NPC talk-to option. @@ -31,10 +32,10 @@ class NPCTalkListener : InteractionListener { on(IntType.NPC,"talk-to","talk","talk to"){ player, node -> val npc = node.asNpc() if(RandomEvents.randomIDs.contains(node.id)){ - if(content.global.ame.RandomEventManager.getInstance(player)!!.event == null || content.global.ame.RandomEventManager.getInstance(player)!!.event!! != node.asNpc()){ + if(AntiMacro.getEventNpc(player) == null || AntiMacro.getEventNpc(player) != node.asNpc()){ player.sendMessage("They aren't interested in talking to you.") } else { - content.global.ame.RandomEventManager.getInstance(player)!!.event!!.talkTo(node.asNpc()) + AntiMacro.getEventNpc(player)?.talkTo(node.asNpc()) } return@on true } diff --git a/Server/src/main/core/game/activity/Cutscene.kt b/Server/src/main/core/game/activity/Cutscene.kt index 9a3bf85b6..992090c4d 100644 --- a/Server/src/main/core/game/activity/Cutscene.kt +++ b/Server/src/main/core/game/activity/Cutscene.kt @@ -23,6 +23,7 @@ import org.rs09.consts.Components import core.ServerConstants import core.api.Event import core.api.utils.PlayerCamera +import core.game.system.timer.impl.AntiMacro import core.tools.SystemLogger import core.game.world.GameWorld import core.tools.Log @@ -238,7 +239,7 @@ abstract class Cutscene(val player: Player) { player.lock() player.hook(Event.SelfDeath, CUTSCENE_DEATH_HOOK) player.logoutListeners["cutscene"] = {player -> player.location = exitLocation; player.getCutscene()?.end() } - content.global.ame.RandomEventManager.getInstance(player)!!.enabled = false + AntiMacro.pause(player) } /** @@ -275,7 +276,7 @@ abstract class Cutscene(val player: Player) { clearNPCs() player.unhook(CUTSCENE_DEATH_HOOK) player.logoutListeners.remove("cutscene") - content.global.ame.RandomEventManager.getInstance(player)?.enabled = true + AntiMacro.unpause(player) PacketRepository.send(MinimapState::class.java, MinimapStateContext(player, 0)) try { endActions?.invoke() diff --git a/Server/src/main/core/game/node/entity/Entity.java b/Server/src/main/core/game/node/entity/Entity.java index c13e4bb2d..cd4ad0a0a 100644 --- a/Server/src/main/core/game/node/entity/Entity.java +++ b/Server/src/main/core/game/node/entity/Entity.java @@ -105,7 +105,7 @@ public abstract class Entity extends Node { * The reward locks. */ private final ActionLocks locks = new ActionLocks(); - public final ScriptProcessor scripts = new ScriptProcessor(this); + public ScriptProcessor scripts = new ScriptProcessor(this); public final int[] clocks = new int[10]; diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java index 909123d6d..c0d498e8f 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -219,7 +219,7 @@ public class Player extends Entity { /** * The quest repository. */ - private QuestRepository questRepository = new QuestRepository(this); + public QuestRepository questRepository = new QuestRepository(this); /** * The prayer manager. @@ -239,7 +239,7 @@ public class Player extends Entity { /** * The saved data. */ - private SavedData savedData = new SavedData(this); + public SavedData savedData = new SavedData(this); /** * The request manager. diff --git a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt index b28353be4..e5984d7e2 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt @@ -39,7 +39,7 @@ class PlayerSaveParser(val player: Player) { var saveFile: JSONObject? = null var read = true - init { + fun parse() { val JSON = File(ServerConstants.PLAYER_SAVE_PATH + player.name + ".json") if(JSON.exists()) { @@ -49,36 +49,38 @@ class PlayerSaveParser(val player: Player) { if (read) { saveFile = parser.parse(reader) as JSONObject } + + if (read) { + parseData() + } } - fun parse() { - if (read) { - parseCore() - parseAttributes() - parseSkills() - parseSettings() - parseQuests() - parseAppearance() - parseGrave() - parseVarps() - parseStates() - parseSpellbook() - parseSavedData() - parseAutocastSpell() - parseFarming() - parseConfigs() - parseMonitor() - parseMusic() - parseFamiliars() - parseBankPin() - parseHouse() - parseIronman() - parseEmoteManager() - parseStatistics() - parseAchievements() - parsePouches() - parsePouches() - } + fun parseData() { + parseCore() + parseAttributes() + parseSkills() + parseSettings() + parseQuests() + parseAppearance() + parseGrave() + parseVarps() + parseStates() + parseSpellbook() + parseSavedData() + parseAutocastSpell() + parseFarming() + parseConfigs() + parseMonitor() + parseMusic() + parseFamiliars() + parseBankPin() + parseHouse() + parseIronman() + parseEmoteManager() + parseStatistics() + parseAchievements() + parsePouches() + parsePouches() } fun runContentHooks() diff --git a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt index b85e58a2e..29b97f4cd 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt @@ -34,7 +34,7 @@ class PlayerSaver (val player: Player){ companion object { val contentHooks = ArrayList() } - private fun populate(): JSONObject { + fun populate(): JSONObject { val saveFile = JSONObject() saveCoreData(saveFile) saveSkills(saveFile) diff --git a/Server/src/main/core/game/node/entity/skill/Skills.java b/Server/src/main/core/game/node/entity/skill/Skills.java index eafb208d9..bb0cc7d27 100644 --- a/Server/src/main/core/game/node/entity/skill/Skills.java +++ b/Server/src/main/core/game/node/entity/skill/Skills.java @@ -26,6 +26,7 @@ import org.rs09.consts.Items; import java.nio.ByteBuffer; import java.util.ArrayList; +import static core.api.ContentAPIKt.getWorldTicks; import static java.lang.Math.floor; import static java.lang.Math.max; @@ -119,7 +120,8 @@ public final class Skills { */ private int skillMilestone; - public int lastTrainedSkill = -1; + public int lastTrainedSkill = -1; + public int lastXpGain = 0; /** * Constructs a new {@code Skills} {@code Object}. @@ -271,7 +273,8 @@ public final class Skills { lastUpdateXp = this.experience.clone(); lastUpdate = GameWorld.getTicks(); } - lastTrainedSkill = slot; + lastTrainedSkill = slot; + lastXpGain = getWorldTicks(); } /** diff --git a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt index 14742786b..6c48e0362 100644 --- a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt @@ -546,26 +546,6 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ define("finishbins", Privilege.ADMIN, "", "Finishes any in-progress compost bins."){ player, _ -> } - define("testlady", Privilege.ADMIN){ player, _ -> - content.global.ame.RandomEventManager.getInstance(player)!!.event = RandomEvents.RIVER_TROLL.npc.create(player) - content.global.ame.RandomEventManager.getInstance(player)!!.event!!.init() - } - - define("freak", Privilege.ADMIN){ player, _ -> - content.global.ame.RandomEventManager.getInstance(player)!!.event = RandomEvents.FREAKY_FORESTER.npc.create(player) - content.global.ame.RandomEventManager.getInstance(player)!!.event!!.init() - } - - define("bob", Privilege.ADMIN){ player, _ -> - content.global.ame.RandomEventManager.getInstance(player)!!.event = RandomEvents.EVIL_BOB.npc.create(player) - content.global.ame.RandomEventManager.getInstance(player)!!.event!!.init() - } - - define("revent", Privilege.ADMIN){ player, _ -> - println(player.pulseManager.current) - content.global.ame.RandomEventManager.getInstance(player)!!.fireEvent() - } - define("addcredits", Privilege.ADMIN){ player, _ -> player.details.credits += 100 } diff --git a/Server/src/main/core/game/system/timer/TimerManager.kt b/Server/src/main/core/game/system/timer/TimerManager.kt index e3196a431..81b981345 100644 --- a/Server/src/main/core/game/system/timer/TimerManager.kt +++ b/Server/src/main/core/game/system/timer/TimerManager.kt @@ -56,6 +56,12 @@ class TimerManager (val entity: Entity) { timer.save(obj, entity) root [timer.identifier] = obj } + for (timer in newTimers) { + if (timer !is PersistTimer) continue + val obj = JSONObject() + timer.save(obj, entity) + root [timer.identifier] = obj + } } fun parseTimers (root: JSONObject) { diff --git a/Server/src/main/core/game/system/timer/TimerRegistry.kt b/Server/src/main/core/game/system/timer/TimerRegistry.kt index d687474e4..3aaa46878 100644 --- a/Server/src/main/core/game/system/timer/TimerRegistry.kt +++ b/Server/src/main/core/game/system/timer/TimerRegistry.kt @@ -30,7 +30,6 @@ object TimerRegistry { @JvmStatic fun addAutoTimers (entity: Entity) { - (entity as? Player)?.debug ("Adding auto timers...") for (timer in autoTimers) { if (!hasTimerActive (entity, timer.identifier)) registerTimer (entity, timer.retrieveInstance()) diff --git a/Server/src/main/core/game/system/timer/impl/AntiMacro.kt b/Server/src/main/core/game/system/timer/impl/AntiMacro.kt new file mode 100644 index 000000000..482c5dc54 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/AntiMacro.kt @@ -0,0 +1,174 @@ +package core.game.system.timer.impl + +import content.global.ame.RandomEventNPC +import content.global.ame.RandomEvents +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.node.item.Item +import core.game.system.command.Privilege +import core.game.system.timer.PersistTimer +import core.game.world.map.zone.ZoneRestriction +import core.game.world.repository.Repository +import core.tools.RandomFunction +import org.json.simple.JSONObject + +class AntiMacro : PersistTimer(0, "antimacro", isAuto = true), Commands { + var paused = false + var nextRandom: RandomEvents? = null + + override fun run(entity: Entity): Boolean { + if (entity !is Player) return false + + setNextExecution() + + if (!canSpawn(entity)) return true + val eventSelection = rollEventPool(entity) + val eventNpc = eventSelection.npc.create(entity, eventSelection.loot, eventSelection.type) + if (eventNpc.spawnLocation == null) { + entity.debug("[AntiMacro] Attempted to spawn random, but spawnLoc was null.") + return true + } + + eventNpc.init() + setAttribute(entity, EVENT_NPC, eventNpc) + entity.debug("[AntiMacro] Fired ${eventSelection.name}.") + return true + } + + override fun onRegister(entity: Entity) { + if (entity !is Player) + entity.timers.removeTimer(this) + + if (runInterval == 0) + setNextExecution() + } + + override fun save(root: JSONObject, entity: Entity) { + root["ticksRemaining"] = (nextExecution - getWorldTicks()).toString() + } + + override fun parse(root: JSONObject, entity: Entity) { + runInterval = (root["ticksRemaining"]?.toString()?.toIntOrNull() ?: 0) + nextExecution = getWorldTicks() + runInterval + } + + private fun canSpawn(entity: Entity) : Boolean { + if (entity.zoneMonitor.isRestricted(ZoneRestriction.RANDOM_EVENTS)) + return false + + val current = getAttribute(entity, EVENT_NPC, null) + if (current != null) + return false + + if (paused) + return false + + return true + } + + private fun setNextExecution() { + runInterval = RandomFunction.random(MIN_DELAY_TICKS, MAX_DELAY_TICKS + 1) + nextExecution = getWorldTicks() + runInterval + } + + private fun rollEventPool(entity: Entity) : RandomEvents { + if (nextRandom != null) { + val result = nextRandom!! + nextRandom = null + return result + } + + val skillBasedRandom = RandomEvents.getSkillBasedRandomEvent(entity.skills.lastTrainedSkill) + val normalRandom = RandomEvents.getNonSkillRandom() + val roll = RandomFunction.random(100) + + if (roll >= 65 && skillBasedRandom != null && getWorldTicks() - entity.skills.lastXpGain < 250) + return skillBasedRandom + return normalRandom + } + + override fun defineCommands() { + define("revent", Privilege.ADMIN, "::revent [-p] player name [-e event name]", "Spawns a random event for the target player.
Optional -e parameter to pass a specific event.") {player, args -> + if (args.size == 1) { + val possible = RandomEvents.values() + for (event in possible) { + notify(player, event.name.lowercase()) + } + return@define + } + + val arg = parseCommandArgs(args.joinToString(" ")) + val target = Repository.getPlayerByName(arg.targetPlayer) + if (target == null) + reject(player, "Unable to find user ${arg.targetPlayer}.") + + forceEvent(target!!, arg.targetEvent) + } + } + + data class CommandArgs (val targetPlayer: String, val targetEvent: RandomEvents?) + + companion object { + const val EVENT_NPC = "re-npc" + const val MIN_DELAY_TICKS = 3000 + const val MAX_DELAY_TICKS = 9000 + + fun terminateEventNpc (player: Player) { + getEventNpc(player)?.terminate() + } + + fun rollEventLoot (player: Player) : ArrayList { + return getEventNpc(player)?.loot?.roll(player) ?: ArrayList() + } + + fun getEventNpc (player: Player) : RandomEventNPC? { + return getAttribute(player, EVENT_NPC, null) + } + + fun pause (player: Player) { + val timer = getTimer(player) ?: return + timer.paused = true + } + + fun unpause (player: Player) { + val timer = getTimer(player) ?: return + timer.paused = false + } + + fun forceEvent (player: Player, event: RandomEvents? = null) { + val timer = getTimer(player) ?: return + timer.nextExecution = getWorldTicks() + timer.nextRandom = event + } + + fun parseCommandArgs (args: String, commandName: String = "revent") : CommandArgs { + val tokens = args.split(" ") + val modeTokens = arrayOf("-p", "-e") + + var userString = "" + var eventString = "" + var lastMode = "-p" + + for (token in tokens) { + when (token) { + commandName -> continue + in modeTokens -> lastMode = token + else -> when (lastMode) { + "-p" -> userString += "$token " + "-e" -> eventString += "$token " + } + } + } + + val username = userString.trim().lowercase().replace(" ", "_") + val eventName = eventString.trim().uppercase().replace(" ", "_") + + var event: RandomEvents? = null + + try { event = RandomEvents.valueOf(eventName) } catch (_: Exception) {} + + return CommandArgs(username, event) + } + } +} \ No newline at end of file diff --git a/Server/src/test/kotlin/TestUtils.kt b/Server/src/test/kotlin/TestUtils.kt index 20d7d28f0..42e51535b 100644 --- a/Server/src/test/kotlin/TestUtils.kt +++ b/Server/src/test/kotlin/TestUtils.kt @@ -1,29 +1,31 @@ -import content.global.ame.RandomEventManager import content.global.skill.farming.timers.CropGrowth +import core.ServerConstants +import core.api.log import core.cache.Cache import core.cache.crypto.ISAACCipher import core.cache.crypto.ISAACPair -import core.game.node.entity.player.Player -import core.game.node.entity.player.info.PlayerDetails -import core.game.node.entity.player.info.Rights -import core.game.node.entity.player.link.IronmanMode -import core.game.node.item.Item -import core.net.IoSession -import core.net.packet.IoBuffer -import org.rs09.consts.Items -import core.ServerConstants -import core.api.log +import core.game.interaction.ScriptProcessor import core.game.node.Node import core.game.node.entity.combat.equipment.WeaponInterface import core.game.node.entity.npc.NPC -import core.game.node.entity.skill.SkillBonus +import core.game.node.entity.player.Player +import core.game.node.entity.player.VarpManager +import core.game.node.entity.player.info.PlayerDetails +import core.game.node.entity.player.info.Rights +import core.game.node.entity.player.info.login.PlayerSaveParser +import core.game.node.entity.player.info.login.PlayerSaver +import core.game.node.entity.player.link.IronmanMode +import core.game.node.entity.player.link.SavedData +import core.game.node.entity.player.link.quest.QuestRepository +import core.game.node.entity.skill.Skills import core.game.node.item.GroundItem +import core.game.node.item.Item import core.game.shops.Shop import core.game.shops.ShopItem -import core.tools.SystemLogger import core.game.system.config.ConfigParser import core.game.system.config.ServerConfigParser import core.game.system.timer.TimerRegistry +import core.game.system.timer.impl.AntiMacro import core.game.system.timer.impl.Disease import core.game.system.timer.impl.Poison import core.game.world.GameWorld @@ -31,10 +33,11 @@ import core.game.world.map.Location import core.game.world.map.RegionManager import core.game.world.repository.Repository import core.game.world.update.UpdateSequence +import core.net.IoSession +import core.net.packet.IoBuffer import core.net.packet.PacketProcessor import core.tools.Log -import org.rs09.consts.Scenery -import java.io.Closeable +import org.rs09.consts.Items import java.net.URI import java.nio.ByteBuffer @@ -85,6 +88,7 @@ object TestUtils { TimerRegistry.registerTimer(Poison()) TimerRegistry.registerTimer(Disease()) TimerRegistry.registerTimer(CropGrowth()) + TimerRegistry.registerTimer(AntiMacro()) } fun loadFile(path: String) : URI? { @@ -111,12 +115,17 @@ object TestUtils { class MockPlayer(name: String) : Player(PlayerDetails(name)), AutoCloseable { var hasInit = false - init { + init { configureBasicProperties(); flagTutComplete(false); init(); flagTutComplete(true) } + + fun configureBasicProperties() { this.details.session = MockSession() this.location = ServerConstants.HOME_LOCATION this.properties.attackStyle = WeaponInterface.AttackStyle(0, WeaponInterface.BONUS_CRUSH) - init() - this.setAttribute("tutorial:complete", true) + } + + fun flagTutComplete(complete: Boolean) { + this.setAttribute("/save:tutorial:complete", complete) + this.setAttribute("/save:rules:confirmed", complete) } override fun update() { @@ -140,6 +149,37 @@ class MockPlayer(name: String) : Player(PlayerDetails(name)), AutoCloseable { RegionManager.move(this) this.playerFlags.lastSceneGraph = location } + + fun relog(ticksToWait: Int = -1) { + val json = PlayerSaver(this).populate() + val parse = PlayerSaveParser(this) + parse.saveFile = json + + close() + timers.clearTimers() + inventory.clear() + bank.clear() + equipment.clear() + skills = Skills(this) + savedData = SavedData(this) + questRepository = QuestRepository(this) + varpManager = VarpManager(this) + varpMap.clear() + saveVarp.clear() + scripts = ScriptProcessor(this) + clearAttributes() + hasInit = false + + if (ticksToWait > 0) TestUtils.advanceTicks(ticksToWait, false) + + configureBasicProperties() + parse.parseData() + init() + } + + override fun debug(string: String?) { + log (this::class.java, Log.DEBUG, "[$name] -> Received Debug: $string") + } } class MockSession : IoSession(null, null) { diff --git a/Server/src/test/kotlin/content/RandomEventManagerTests.kt b/Server/src/test/kotlin/content/RandomEventManagerTests.kt deleted file mode 100644 index 81e700696..000000000 --- a/Server/src/test/kotlin/content/RandomEventManagerTests.kt +++ /dev/null @@ -1,81 +0,0 @@ -package content - -import TestUtils -import content.global.ame.RandomEventManager -import core.game.node.item.Item -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import org.rs09.consts.Items -import content.global.ame.RandomEventNPC -import core.game.world.GameWorld - -class RandomEventManagerTests { - init { - TestUtils.preTestSetup() - } - - @Test fun loginShouldEnableManager() { - val p = TestUtils.getMockPlayer("Bill") - RandomEventManager().login(p) - val manager = content.global.ame.RandomEventManager.getInstance(p) - Assertions.assertNotNull(manager) - Assertions.assertEquals(true, manager!!.enabled) - } - - @Test fun loginShouldSetNextSpawn() { - val p = TestUtils.getMockPlayer("Bill") - RandomEventManager().login(p) - val manager = content.global.ame.RandomEventManager.getInstance(p) - Assertions.assertNotNull(manager) - Assertions.assertEquals(true, manager!!.nextSpawn > GameWorld.ticks) - } - - @Test fun shouldSpawnRandomEventWithinMAXTICKSGivenNoRestrictions() { - val p = TestUtils.getMockPlayer("Bill") - p.setAttribute("tutorial:complete", true) //tutorial has to be complete to spawn randoms - RandomEventManager.MIN_DELAY_TICKS = 10 - RandomEventManager.MAX_DELAY_TICKS = 20 - RandomEventManager().login(p) - TestUtils.advanceTicks(RandomEventManager.MAX_DELAY_TICKS + 5) - RandomEventManager.MIN_DELAY_TICKS = 3000 - RandomEventManager.MAX_DELAY_TICKS = 9000 - Assertions.assertNotNull(p.getAttribute("re-npc", null)) - } - - @Test fun teleportAndNotePunishmentShouldNotAffectAlreadyNotedItems() { - val p = TestUtils.getMockPlayer("Shitforbrains") - p.setAttribute("tutorial:complete", true) - RandomEventManager().login(p) - - p.inventory.add(Item(Items.RAW_SHARK_384, 1000)) - content.global.ame.RandomEventManager.getInstance(p)?.fireEvent() - p.getAttribute("re-npc")!!.noteAndTeleport() - - Assertions.assertEquals(1000, p.inventory.getAmount(Items.RAW_SHARK_384)) - } - - @Test fun teleportAndNotePunishmentShouldNoteNotableUnnotedItems() { - val p = TestUtils.getMockPlayer("shitforbrains2") - p.setAttribute("tutorial:complete", true) - RandomEventManager().login(p) - - p.inventory.add(Item(4151, 5)) - content.global.ame.RandomEventManager.getInstance(p)?.fireEvent() - p.getAttribute("re-npc")!!.noteAndTeleport() - - Assertions.assertEquals(5, p.inventory.getAmount(4152)) - Assertions.assertEquals(0, p.inventory.getAmount(4151)) - } - - @Test fun teleportAndNotePunishmentShouldNotAffectUnnotableItems() { - val p = TestUtils.getMockPlayer("shitforbrains3") - p.setAttribute("tutorial:complete", true) - RandomEventManager().login(p) - - p.inventory.add(Item(Items.AIR_RUNE_556, 30)) - content.global.ame.RandomEventManager.getInstance(p)?.fireEvent() - p.getAttribute("re-npc")!!.noteAndTeleport() - - Assertions.assertEquals(30, p.inventory.getAmount(Items.AIR_RUNE_556)) - } -} \ No newline at end of file diff --git a/Server/src/test/kotlin/content/RandomEventTests.kt b/Server/src/test/kotlin/content/RandomEventTests.kt new file mode 100644 index 000000000..efd98a335 --- /dev/null +++ b/Server/src/test/kotlin/content/RandomEventTests.kt @@ -0,0 +1,217 @@ +package content + +import TestUtils +import content.global.ame.RandomEventNPC +import content.global.ame.RandomEvents +import core.api.* +import core.game.system.timer.impl.AntiMacro +import core.game.world.map.Location +import core.game.world.map.zone.impl.WildernessZone +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.rs09.consts.Items +import org.rs09.consts.NPCs + +class RandomEventTests { + init { + TestUtils.preTestSetup() + } + + @Test fun loginShouldRegisterRandomEventTimer() { + TestUtils.getMockPlayer("antimacroAutoRegister").use { p -> + Assertions.assertNotNull(getTimer(p)) + } + } + + @Test fun loginShouldSetNextSpawn() { + TestUtils.getMockPlayer("antimacroautosetsnextspawnifunset").use { p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer is null.") + Assertions.assertNotEquals(getWorldTicks(), timer.nextExecution) + Assertions.assertEquals(true, timer.nextExecution - getWorldTicks() >= AntiMacro.MIN_DELAY_TICKS) + Assertions.assertEquals(true, timer.nextExecution - getWorldTicks() <= AntiMacro.MAX_DELAY_TICKS) + } + } + + @Test fun remainingDelayShouldPersistRelog() { + TestUtils.getMockPlayer("antimacrotimeremainingpersists").use { p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer is null.") + timer.nextExecution = getWorldTicks() + 666 + p.relog(ticksToWait = 100) + Assertions.assertEquals(getWorldTicks() + 666, getTimer(p)!!.nextExecution) + } + } + + @Test fun delayShouldBeRestartedOnceDepleted() { + TestUtils.getMockPlayer("delayrestartoncedepleted").use { p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer is null.") + TestUtils.advanceTicks(2, false) + timer.nextExecution = 5 //run in 5 ticks + TestUtils.advanceTicks(5, false) + Assertions.assertEquals(true, timer.nextExecution - getWorldTicks() >= AntiMacro.MIN_DELAY_TICKS) + Assertions.assertEquals(true, timer.nextExecution - getWorldTicks() <= AntiMacro.MAX_DELAY_TICKS) + } + } + + @Test fun shouldSpawnRandomEventGivenNoRestrictions() { + TestUtils.getMockPlayer("antimacroshouldspawnrandom").use {p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer is null!") + TestUtils.advanceTicks(5, false) + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + Assertions.assertNotNull(p.getAttribute(AntiMacro.EVENT_NPC, null)) + Assertions.assertEquals(true, p.getAttribute(AntiMacro.EVENT_NPC).isActive) + } + } + + @Test fun teleportAndNotePunishmentShouldNotAffectAlreadyNotedItems() { + TestUtils.getMockPlayer("teleportpunishment1").use {p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer is null!") + TestUtils.advanceTicks(5, false) + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + + addItem(p, Items.RAW_SHARK_384, 1000) + getAttribute(p, AntiMacro.EVENT_NPC, null)!!.noteAndTeleport() + + Assertions.assertEquals(1000, amountInInventory(p, Items.RAW_SHARK_384)) + } + } + + @Test fun teleportAndNotePunishmentShouldNoteNotableUnnotedItems() { + TestUtils.getMockPlayer("teleportpunishment2").use {p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer is null!") + TestUtils.advanceTicks(5, false) + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + + addItem(p, Items.ABYSSAL_WHIP_4151, 5) + getAttribute(p, AntiMacro.EVENT_NPC, null)!!.noteAndTeleport() + + Assertions.assertEquals(5, amountInInventory(p, Items.ABYSSAL_WHIP_4152)) + Assertions.assertEquals(0, amountInInventory(p, Items.ABYSSAL_WHIP_4151)) + } + } + + @Test fun teleportAndNotePunishmentShouldNotAffectUnnotableItems() { + TestUtils.getMockPlayer("teleportpunishment3").use {p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer is null!") + TestUtils.advanceTicks(5, false) + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + + addItem(p, Items.AIR_RUNE_556, 30) + getAttribute(p, AntiMacro.EVENT_NPC, null)!!.noteAndTeleport() + + Assertions.assertEquals(30, amountInInventory(p, Items.AIR_RUNE_556)) + } + } + + @Test fun randomEventShouldNotSpawnInEventRestrictedArea() { + + TestUtils.getMockPlayer("antimacronospawninrestrictedzone").use { p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer was null!") + TestUtils.advanceTicks(5, false) + + //Wilderness is Random Event restricted + WildernessZone.getInstance().configure() + val loc = Location.create(3131, 3595) + p.location = loc + + Assertions.assertEquals(loc, p.location) + + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + Assertions.assertNull(AntiMacro.getEventNpc(p)) + } + } + + @Test fun randomEventShouldNotSpawnIfOneAlreadyActive() { + TestUtils.getMockPlayer("antimacronospawnifalreadyhas").use { p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer was null!") + TestUtils.advanceTicks(5, false) + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + Assertions.assertNotNull(AntiMacro.getEventNpc(p)) + + val previousEvent = AntiMacro.getEventNpc(p) + + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + Assertions.assertNotNull(AntiMacro.getEventNpc(p)) + Assertions.assertEquals(previousEvent, AntiMacro.getEventNpc(p)) + } + } + + @Test fun randomEventShouldSpawnIfCurrentOneIsInactive() { + TestUtils.getMockPlayer("antimacrospawnifcurrentinactive").use { p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer was null!") + TestUtils.advanceTicks(5, false) + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + Assertions.assertNotNull(AntiMacro.getEventNpc(p)) + + val previousEvent = AntiMacro.getEventNpc(p) + AntiMacro.terminateEventNpc(p) + + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + Assertions.assertNotNull(AntiMacro.getEventNpc(p)) + Assertions.assertNotEquals(previousEvent, AntiMacro.getEventNpc(p)) + } + } + + @Test fun randomEventSystemShouldSupportPauseAndUnpause() { + TestUtils.getMockPlayer("antimacropause").use { p -> + val timer = getTimer(p) ?: Assertions.fail("AntiMacro timer was null!") + TestUtils.advanceTicks(5, false) + timer.nextExecution = getWorldTicks() + 5 + + AntiMacro.pause(p) + TestUtils.advanceTicks(10, false) + Assertions.assertNull(AntiMacro.getEventNpc(p)) + + AntiMacro.unpause(p) + timer.nextExecution = getWorldTicks() + 5 + TestUtils.advanceTicks(10, false) + Assertions.assertNotNull(AntiMacro.getEventNpc(p)) + } + } + + @Test fun shouldBeAbleToForceRandomEvent() { + TestUtils.getMockPlayer("antimacroforcerand").use { p -> + TestUtils.advanceTicks(5, false) + AntiMacro.forceEvent(p) + TestUtils.advanceTicks(1, false) + Assertions.assertNotNull(AntiMacro.getEventNpc(p)) + } + } + + @Test fun shouldBeAbleToForceSpecificRandomEvent() { + TestUtils.getMockPlayer("antimacroforcespecific").use { p -> + TestUtils.advanceTicks(5, false) + AntiMacro.forceEvent(p, RandomEvents.TREE_SPIRIT) + TestUtils.advanceTicks(1, false) + Assertions.assertNotNull(AntiMacro.getEventNpc(p)) + Assertions.assertEquals(NPCs.TREE_SPIRIT_438, AntiMacro.getEventNpc(p)?.originalId) + } + } + + @Test fun parseAntiMacroCommandArgsShouldReturnExpectedValues() { + val testData = arrayOf( + Triple("revent -p test", "test", null), + Triple("revent -p test ", "test", null), + Triple("revent -p test -e certer ", "test", RandomEvents.CERTER), + Triple("revent -p test_user -e Sandwich lady", "test_user", RandomEvents.SANDWICH_LADY), + Triple("revent -p test user -e sandwich Lady ", "test_user", RandomEvents.SANDWICH_LADY), + Triple("revent -e sandwich Lady -p test user ", "test_user", RandomEvents.SANDWICH_LADY), + Triple("revent test", "test", null), + Triple("revent test -e sAndwich Lady", "test", RandomEvents.SANDWICH_LADY) + ) + + for ((commandStr, expectedUser, expectedEvent) in testData) { + val (resultUser, resultEvent) = AntiMacro.parseCommandArgs(commandStr, "revent") + Assertions.assertEquals(expectedUser, resultUser) + Assertions.assertEquals(expectedEvent, resultEvent) + } + } +} \ No newline at end of file