From 661390e66f402e9ad052e3e1cffe32227b42ca53 Mon Sep 17 00:00:00 2001 From: Ceikry Date: Sat, 29 Jul 2023 04:51:06 +0000 Subject: [PATCH] Implemented timer subsystem to eventually replace pulses Authentic subsystem supports saving/loading arbitrary data and resuming timer countdowns Lots of documented CAPI functions for working with these new timers Converted poison to new timers Converted disease to new timers Converted farming to new timers (CropGrowth and Compost) Farming now syncs with 5 minute intervals on realtime clocks (authentic) Converted seedling growth to new timers Converted shooting star mining bonus to new timers Converted entity freezing to new timers Converted incubation to new timers Incubation now supports using both incubators (so 2 eggs at once) Converted miasmic states to the new timers Converted god spell charged state to new timers Converted teleblock to new timers Converted skulled state to new timers Converted Enhanced Excalibur special attack effect to new timers Converted passive stat restoration to timers Converted multicannon firing and decay to a timer --- .../content/data/consumables/Consumables.java | 21 +- .../consumables/effects/AddTimerEffect.kt | 13 ++ .../effects/RemoveStateEffect.java | 25 --- .../consumables/effects/RemoveTimerEffect.kt | 11 + .../cchallange/ChampionChallengeListener.kt | 4 +- .../shootingstar/ShootingStarState.kt | 47 ----- .../global/activity/shootingstar/StarBonus.kt | 28 +++ .../shootingstar/StarSpriteDialogue.kt | 7 +- .../handlers/item/KeldagrimVotingBond.java | 188 ----------------- .../special/DragonfireSwingHandler.java | 7 +- .../special/ExcaliburSpecialHandler.java | 5 +- .../special/IceCleaveSpecialHandler.java | 5 +- .../special/ShoveSpecialHandler.java | 1 - .../item/withobject/IncubatorPlugin.java | 124 ------------ .../global/skill/farming/CompostBin.kt | 4 + .../global/skill/farming/CompostBins.kt | 10 +- .../global/skill/farming/FarmingPatch.kt | 10 +- .../global/skill/farming/FarmingState.kt | 156 ++------------- .../content/global/skill/farming/PatchType.kt | 2 +- .../global/skill/farming/SeedOnPlantPot.kt | 23 +-- .../global/skill/farming/SeedlingState.kt | 83 -------- .../global/skill/farming/timers/Compost.kt | 65 ++++++ .../global/skill/farming/timers/CropGrowth.kt | 148 ++++++++++++++ .../skill/farming/timers/SeedlingGrowth.kt | 74 +++++++ .../skill/gather/mining/MiningListener.kt | 5 +- .../global/skill/magic/ancient/IceSpells.java | 7 +- .../skill/magic/ancient/MiasmicSpells.java | 7 +- .../skill/magic/ancient/SmokeSpells.java | 5 +- .../skill/magic/lunar/LunarListeners.kt | 15 +- .../skill/magic/modern/ChargeSpell.java | 6 +- .../magic/modern/GodspellChargedState.kt | 46 ----- .../global/skill/magic/modern/SpellCharge.kt | 16 ++ .../skill/magic/modern/TeleblockSpell.java | 5 +- .../skill/prayer/PrayerAltarPlugin.java | 1 - .../summoning/familiar/BloatedLeechNPC.java | 5 +- .../familiar/MinotaurFamiliarNPC.java | 1 - .../summoning/familiar/SpiritScorpionNPC.java | 5 +- .../summoning/familiar/StrangerPlantNPC.java | 5 +- .../familiar/UnicornStallionNPC.java | 7 +- .../skill/summoning/pet/IncubatorHandler.kt | 76 +++++++ .../skill/summoning/pet/IncubatorState.kt | 65 ------ .../skill/summoning/pet/IncubatorTimer.kt | 98 +++++++++ .../skill/thieving/ThievingListeners.kt | 3 +- .../content/global/state/DiseasedState.kt | 53 ----- .../bountyhunter/BountyLocateSpell.java | 7 +- .../castlewars/areas/CastleWarsArea.kt | 11 +- .../castlewars/areas/CastleWarsGameArea.kt | 7 +- .../castlewars/areas/CastleWarsWaitingArea.kt | 9 +- .../content/minigame/duel/DuelSession.java | 5 +- .../fishingtrawler/FishingTrawlerSession.kt | 7 +- .../PestControlActivityPlugin.java | 7 +- .../pestcontrol/monsters/PCSpinnerNPC.java | 6 +- .../pyramidplunder/PyramidPlunderMinigame.kt | 5 +- .../minigame/vinesweeper/Vinesweeper.kt | 3 +- .../gwd/GWDTsutsarothSwingHandler.java | 7 +- .../waterbirth/handlers/SpinolypNPC.java | 5 +- .../quest/dwarfcannon/dmc/CannonTimer.kt | 34 ++++ .../quest/dwarfcannon/dmc/DMCHandler.java | 67 ++----- .../yanille/handlers/YanilleAgilityDungeon.kt | 3 +- .../handlers/DarkEnergyCoreNPC.java | 5 +- .../wilderness/handlers/SkulledState.kt | 45 ----- .../revenants/RevenantCombatHandler.java | 16 +- Server/src/main/core/api/ContentAPI.kt | 189 +++++++++++++++--- Server/src/main/core/api/Event.kt | 4 +- Server/src/main/core/game/event/EventHook.kt | 4 +- Server/src/main/core/game/event/Events.kt | 5 +- .../main/core/game/node/entity/Entity.java | 26 +-- .../game/node/entity/combat/CombatPulse.kt | 5 +- .../node/entity/combat/MeleeSwingHandler.kt | 3 +- .../node/entity/combat/RangeSwingHandler.kt | 5 +- .../entity/combat/equipment/BoltEffect.java | 5 +- .../entity/combat/equipment/FireType.java | 9 +- .../node/entity/combat/spell/SpellType.java | 7 +- .../game/node/entity/impl/WalkingQueue.java | 4 +- .../main/core/game/node/entity/npc/NPC.java | 1 + .../core/game/node/entity/player/Player.java | 4 +- .../player/info/login/LoginConfiguration.java | 1 - .../player/info/login/PlayerSaveParser.kt | 4 + .../entity/player/info/login/PlayerSaver.kt | 7 +- .../node/entity/player/link/Settings.java | 16 -- .../node/entity/player/link/SkullManager.java | 6 +- .../entity/player/link/prayer/PrayerType.java | 4 +- .../node/entity/skill/SkillRestoration.java | 83 -------- .../core/game/node/entity/skill/Skills.java | 36 ---- .../game/node/entity/state/EntityState.java | 66 ------ .../game/node/entity/state/StateManager.java | 138 ------------- .../game/node/entity/state/StateRepository.kt | 2 +- .../entity/state/impl/FrozenStatePulse.java | 95 --------- .../entity/state/impl/HealOverTimePulse.java | 86 -------- .../entity/state/impl/MiasmicStatePulse.java | 90 --------- .../entity/state/impl/PoisonStatePulse.java | 120 ----------- .../entity/state/impl/SkullStatePulse.java | 70 ------- .../entity/state/impl/StunStatePulse.java | 96 --------- .../state/impl/TeleblockStatePulse.java | 105 ---------- .../system/command/sets/MiscCommandSet.kt | 13 +- .../system/command/sets/StatAttributeKeys.kt | 2 + .../core/game/system/timer/PersistTimer.kt | 23 +++ .../main/core/game/system/timer/RSTimer.kt | 44 ++++ .../core/game/system/timer/TimerManager.kt | 124 ++++++++++++ .../core/game/system/timer/TimerRegistry.kt | 50 +++++ .../core/game/system/timer/impl/Disease.kt | 46 +++++ .../core/game/system/timer/impl/Frozen.kt | 48 +++++ .../game/system/timer/impl/FrozenImmunity.kt | 38 ++++ .../game/system/timer/impl/HealOverTime.kt | 47 +++++ .../core/game/system/timer/impl/Miasmic.kt | 27 +++ .../game/system/timer/impl/MiasmicImmunity.kt | 24 +++ .../core/game/system/timer/impl/Poison.kt | 79 ++++++++ .../game/system/timer/impl/PoisonImmunity.kt | 53 +++++ .../game/system/timer/impl/SkillRestore.kt | 101 ++++++++++ .../core/game/system/timer/impl/Skulled.kt | 27 +++ .../core/game/system/timer/impl/Teleblock.kt | 24 +++ Server/src/main/core/game/world/GameWorld.kt | 3 +- .../core/game/world/repository/Repository.kt | 7 +- Server/src/main/core/plugin/ClassScanner.kt | 13 ++ 114 files changed, 1718 insertions(+), 2112 deletions(-) create mode 100644 Server/src/main/content/data/consumables/effects/AddTimerEffect.kt delete mode 100644 Server/src/main/content/data/consumables/effects/RemoveStateEffect.java create mode 100644 Server/src/main/content/data/consumables/effects/RemoveTimerEffect.kt delete mode 100644 Server/src/main/content/global/activity/shootingstar/ShootingStarState.kt create mode 100644 Server/src/main/content/global/activity/shootingstar/StarBonus.kt delete mode 100644 Server/src/main/content/global/handlers/item/KeldagrimVotingBond.java delete mode 100644 Server/src/main/content/global/handlers/item/withobject/IncubatorPlugin.java delete mode 100644 Server/src/main/content/global/skill/farming/SeedlingState.kt create mode 100644 Server/src/main/content/global/skill/farming/timers/Compost.kt create mode 100644 Server/src/main/content/global/skill/farming/timers/CropGrowth.kt create mode 100644 Server/src/main/content/global/skill/farming/timers/SeedlingGrowth.kt delete mode 100644 Server/src/main/content/global/skill/magic/modern/GodspellChargedState.kt create mode 100644 Server/src/main/content/global/skill/magic/modern/SpellCharge.kt create mode 100644 Server/src/main/content/global/skill/summoning/pet/IncubatorHandler.kt delete mode 100644 Server/src/main/content/global/skill/summoning/pet/IncubatorState.kt create mode 100644 Server/src/main/content/global/skill/summoning/pet/IncubatorTimer.kt delete mode 100644 Server/src/main/content/global/state/DiseasedState.kt create mode 100644 Server/src/main/content/region/kandarin/quest/dwarfcannon/dmc/CannonTimer.kt delete mode 100644 Server/src/main/content/region/wilderness/handlers/SkulledState.kt delete mode 100644 Server/src/main/core/game/node/entity/skill/SkillRestoration.java delete mode 100644 Server/src/main/core/game/node/entity/state/EntityState.java delete mode 100644 Server/src/main/core/game/node/entity/state/StateManager.java delete mode 100644 Server/src/main/core/game/node/entity/state/impl/FrozenStatePulse.java delete mode 100644 Server/src/main/core/game/node/entity/state/impl/HealOverTimePulse.java delete mode 100644 Server/src/main/core/game/node/entity/state/impl/MiasmicStatePulse.java delete mode 100644 Server/src/main/core/game/node/entity/state/impl/PoisonStatePulse.java delete mode 100644 Server/src/main/core/game/node/entity/state/impl/SkullStatePulse.java delete mode 100644 Server/src/main/core/game/node/entity/state/impl/StunStatePulse.java delete mode 100644 Server/src/main/core/game/node/entity/state/impl/TeleblockStatePulse.java create mode 100644 Server/src/main/core/game/system/timer/PersistTimer.kt create mode 100644 Server/src/main/core/game/system/timer/RSTimer.kt create mode 100644 Server/src/main/core/game/system/timer/TimerManager.kt create mode 100644 Server/src/main/core/game/system/timer/TimerRegistry.kt create mode 100644 Server/src/main/core/game/system/timer/impl/Disease.kt create mode 100644 Server/src/main/core/game/system/timer/impl/Frozen.kt create mode 100644 Server/src/main/core/game/system/timer/impl/FrozenImmunity.kt create mode 100644 Server/src/main/core/game/system/timer/impl/HealOverTime.kt create mode 100644 Server/src/main/core/game/system/timer/impl/Miasmic.kt create mode 100644 Server/src/main/core/game/system/timer/impl/MiasmicImmunity.kt create mode 100644 Server/src/main/core/game/system/timer/impl/Poison.kt create mode 100644 Server/src/main/core/game/system/timer/impl/PoisonImmunity.kt create mode 100644 Server/src/main/core/game/system/timer/impl/SkillRestore.kt create mode 100644 Server/src/main/core/game/system/timer/impl/Skulled.kt create mode 100644 Server/src/main/core/game/system/timer/impl/Teleblock.kt diff --git a/Server/src/main/content/data/consumables/Consumables.java b/Server/src/main/content/data/consumables/Consumables.java index 0b60525f6..22a5eeb32 100644 --- a/Server/src/main/content/data/consumables/Consumables.java +++ b/Server/src/main/content/data/consumables/Consumables.java @@ -4,7 +4,6 @@ import content.data.consumables.effects.*; import core.game.consumable.*; import org.rs09.consts.Items; import core.game.node.entity.player.link.diary.DiaryType; -import core.game.node.entity.state.EntityState; import core.game.world.update.flag.context.Animation; import core.game.node.entity.skill.Skills; import content.data.consumables.effects.KegOfBeerEffect; @@ -164,7 +163,7 @@ public enum Consumables { LIME_SLICES(new Food(new int[] {2124}, new HealingEffect(2))), PEACH(new Food(new int[] {6883}, new HealingEffect(8))), WHITE_TREE_FRUIT(new Food(new int[] {6469}, new MultiEffect(new RandomEnergyEffect(5, 10), new HealingEffect(3)))), - STRANGE_FRUIT(new Food(new int[] {464}, new MultiEffect(new RemoveStateEffect(EntityState.POISONED.ordinal()), new EnergyEffect(30)))), + STRANGE_FRUIT(new Food(new int[] {464}, new MultiEffect(new RemoveTimerEffect("poison"), new EnergyEffect(30)))), /** Gnome Cooking */ TOAD_CRUNCHIES(new Food(new int[] {2217}, new HealingEffect(12))), @@ -321,11 +320,11 @@ public enum Consumables { SUPER_STRENGTH(new Potion(new int[] {2440, 157, 159, 161}, new SkillEffect(Skills.STRENGTH, 3, 0.2))), SUPER_ATTACK(new Potion(new int[] {2436, 145, 147, 149}, new SkillEffect(Skills.ATTACK, 3, 0.2))), SUPER_DEFENCE(new Potion(new int[] {2442, 163, 165, 167}, new SkillEffect(Skills.DEFENCE, 3, 0.2))), - ANTIPOISON(new Potion(new int[] {2446, 175, 177, 179}, new MultiEffect(new SetAttributeEffect("poison:immunity", 143), new RemoveStateEffect(EntityState.POISONED.ordinal())))), - ANTIPOISON_(new Potion(new int[] {5943, 5945, 5947, 5949}, new MultiEffect(new SetAttributeEffect("poison:immunity", 863), new RemoveStateEffect(EntityState.POISONED.ordinal())))), - ANTIPOISON__(new Potion(new int[] {5952, 5954, 5956, 5958}, new MultiEffect(new SetAttributeEffect("poison:immunity", 2000), new RemoveStateEffect(EntityState.POISONED.ordinal())))), - SUPER_ANTIP(new Potion(new int[] {2448, 181, 183, 185}, new MultiEffect(new SetAttributeEffect("poison:immunity", 1000), new RemoveStateEffect(EntityState.POISONED.ordinal())))), - RELICYM(new Potion(new int[] {4842, 4844, 4846, 4848}, new MultiEffect(new SetAttributeEffect("disease:immunity", 300), new RemoveStateEffect("disease")))), + ANTIPOISON(new Potion(new int[] {2446, 175, 177, 179}, new AddTimerEffect("poison:immunity", 143))), + ANTIPOISON_(new Potion(new int[] {5943, 5945, 5947, 5949}, new AddTimerEffect("poison:immunity", 863))), + ANTIPOISON__(new Potion(new int[] {5952, 5954, 5956, 5958}, new AddTimerEffect("poison:immunity", 2000))), + SUPER_ANTIP(new Potion(new int[] {2448, 181, 183, 185}, new AddTimerEffect("poison:immunity", 1000))), + RELICYM(new Potion(new int[] {4842, 4844, 4846, 4848}, new MultiEffect(new SetAttributeEffect("disease:immunity", 300), new RemoveTimerEffect("disease")))), AGILITY(new Potion(new int[] {3032, 3034, 3036, 3038}, new SkillEffect(Skills.AGILITY, 3, 0))), HUNTER(new Potion(new int[] {9998, 10000, 10002, 10004}, new SkillEffect(Skills.HUNTER, 3, 0))), RESTORE(new Potion(new int[] {2430, 127, 129, 131}, new RestoreEffect(10, 0.3))), @@ -338,9 +337,9 @@ public enum Consumables { SUPER_RESTO(new Potion(new int[] {3024, 3026, 3028, 3030}, new MultiEffect(new RestoreEffect(8, 0.25), new PrayerEffect(8, 0.25), new SummoningEffect(8, 0.25)))), ZAMMY_BREW(new Potion(new int[] {2450, 189, 191, 193}, new MultiEffect(new DamageEffect(10, true), new SkillEffect(Skills.ATTACK, 0, 0.15), new SkillEffect(Skills.STRENGTH, 0, 0.25), new SkillEffect(Skills.DEFENCE, 0, -0.1), new RandomPrayerEffect(0, 10)))), ANTIFIRE(new Potion(new int[] {2452, 2454, 2456, 2458}, new SetAttributeEffect("fire:immune", 600, true))), - GUTH_REST(new Potion(new int[] {4417, 4419, 4421, 4423}, new MultiEffect(new RemoveStateEffect(EntityState.POISONED.ordinal()), new EnergyEffect(5), new HealingEffect(5)))), + GUTH_REST(new Potion(new int[] {4417, 4419, 4421, 4423}, new MultiEffect(new RemoveTimerEffect("poison"), new EnergyEffect(5), new HealingEffect(5)))), MAGIC_ESS(new Potion(new int[] {11491, 11489}, new SkillEffect(Skills.MAGIC,3,0))), - SANFEW(new Potion(new int[] {10925, 10927, 10929, 10931}, new MultiEffect(new RestoreEffect(8,0.25), new PrayerEffect(8,0.25), new RemoveStateEffect(EntityState.POISONED.ordinal()), new RemoveStateEffect("disease")))), + SANFEW(new Potion(new int[] {10925, 10927, 10929, 10931}, new MultiEffect(new RestoreEffect(8,0.25), new PrayerEffect(8,0.25), new RemoveTimerEffect("poison"), new RemoveTimerEffect("disease")))), SUPER_ENERGY(new Potion(new int[] {3016, 3018, 3020, 3022}, new EnergyEffect(20))), BLAMISH_OIL(new FakeConsumable(1582, new String[] {"You know... I'd really rather not."})), @@ -348,8 +347,8 @@ public enum Consumables { PRAYERMIX(new BarbarianMix(new int[] {11465, 11467}, new MultiEffect(new PrayerEffect(7, 0.25), new HealingEffect(6)))), ZAMMY_MIX(new BarbarianMix(new int[] {11521, 11523}, new MultiEffect(new DamageEffect(10, true), new SkillEffect(Skills.ATTACK, 0, 0.15), new SkillEffect(Skills.STRENGTH, 0, 0.25), new SkillEffect(Skills.DEFENCE, 0, -0.1), new RandomPrayerEffect(0, 10)))), ATT_MIX(new BarbarianMix(new int[] {11429, 11431}, new MultiEffect(new SkillEffect(Skills.ATTACK, 3, 0.1), new HealingEffect(3)))), - ANTIP_MIX(new BarbarianMix(new int[] {11433, 11435}, new MultiEffect(new RemoveStateEffect(EntityState.POISONED.ordinal()), new SetAttributeEffect("poison:immunity", 143), new HealingEffect(3)))), - RELIC_MIX(new BarbarianMix(new int[] {11437, 11439}, new MultiEffect(new RemoveStateEffect("disease"), new SetAttributeEffect("disease:immunity", 300), new HealingEffect(3)))), + ANTIP_MIX(new BarbarianMix(new int[] {11433, 11435}, new MultiEffect(new AddTimerEffect("poison:immunity", 143), new HealingEffect(3)))), + RELIC_MIX(new BarbarianMix(new int[] {11437, 11439}, new MultiEffect(new RemoveTimerEffect("disease"), new SetAttributeEffect("disease:immunity", 300), new HealingEffect(3)))), STR_MIX(new BarbarianMix(new int[] {11443, 11441}, new MultiEffect(new SkillEffect(Skills.STRENGTH, 3, 0.1), new HealingEffect(3)))), RESTO_MIX(new BarbarianMix(new int[] {11449, 11451}, new MultiEffect(new RestoreEffect(10, 0.3), new HealingEffect(3)))), SUPER_RESTO_MIX(new BarbarianMix(new int [] {11493, 11495}, new MultiEffect(new RestoreEffect(8,0.25), new PrayerEffect(8, 0.25), new SummoningEffect(8, 0.25), new HealingEffect(6)))), diff --git a/Server/src/main/content/data/consumables/effects/AddTimerEffect.kt b/Server/src/main/content/data/consumables/effects/AddTimerEffect.kt new file mode 100644 index 000000000..48b11b475 --- /dev/null +++ b/Server/src/main/content/data/consumables/effects/AddTimerEffect.kt @@ -0,0 +1,13 @@ +package content.data.consumables.effects + +import core.api.* +import core.game.system.timer.impl.PoisonImmunity +import core.game.consumable.ConsumableEffect +import core.game.node.entity.player.Player + +class AddTimerEffect (val identifier: String, vararg val args: Any) : ConsumableEffect() { + override fun activate (p: Player) { + val timer = spawnTimer (identifier, args) ?: return + registerTimer (p, timer) + } +} diff --git a/Server/src/main/content/data/consumables/effects/RemoveStateEffect.java b/Server/src/main/content/data/consumables/effects/RemoveStateEffect.java deleted file mode 100644 index 7c2d5040a..000000000 --- a/Server/src/main/content/data/consumables/effects/RemoveStateEffect.java +++ /dev/null @@ -1,25 +0,0 @@ -package content.data.consumables.effects; - -import core.game.consumable.ConsumableEffect; -import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; - -public class RemoveStateEffect extends ConsumableEffect { - int state = -1; - String statekey; - @Deprecated - public RemoveStateEffect(int state){ - this.state = state; - } - public RemoveStateEffect(String key){ - statekey = key; - } - @Override - public void activate(Player p) { - if(state == -1){ - p.clearState(statekey); - return; - } - p.getStateManager().remove(EntityState.values()[state]); - } -} diff --git a/Server/src/main/content/data/consumables/effects/RemoveTimerEffect.kt b/Server/src/main/content/data/consumables/effects/RemoveTimerEffect.kt new file mode 100644 index 000000000..6d45c35e9 --- /dev/null +++ b/Server/src/main/content/data/consumables/effects/RemoveTimerEffect.kt @@ -0,0 +1,11 @@ +package content.data.consumables.effects + +import core.api.* +import core.game.consumable.ConsumableEffect +import core.game.node.entity.player.Player + +class RemoveTimerEffect (val identifier: String) : ConsumableEffect() { + override fun activate (p: Player) { + removeTimer (p, identifier) + } +} diff --git a/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt b/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt index a854778b7..8cea6d806 100644 --- a/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt +++ b/Server/src/main/content/global/activity/cchallange/ChampionChallengeListener.kt @@ -15,7 +15,6 @@ import core.game.global.action.DoorActionHandler import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.node.entity.player.Player -import core.game.node.entity.state.EntityState import core.game.node.item.Item import core.game.system.task.Pulse import core.game.world.map.Location @@ -174,7 +173,6 @@ class ChampionChallengeListener : InteractionListener, MapArea { override fun pulse(): Boolean { when (counter++) { 1 -> { - player.stateManager.get(EntityState.TELEBLOCK) player.familiarManager.dismiss() } 2 -> DoorActionHandler.handleDoor(player, node.asScenery()) @@ -231,4 +229,4 @@ class ChampionChallengeListener : InteractionListener, MapArea { ZoneRestriction.RANDOM_EVENTS ) } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/activity/shootingstar/ShootingStarState.kt b/Server/src/main/content/global/activity/shootingstar/ShootingStarState.kt deleted file mode 100644 index 0adbf564d..000000000 --- a/Server/src/main/content/global/activity/shootingstar/ShootingStarState.kt +++ /dev/null @@ -1,47 +0,0 @@ -package content.global.activity.shootingstar - -import core.game.node.entity.player.Player -import core.game.node.entity.state.PlayerState -import core.game.node.entity.state.State -import core.game.system.task.Pulse -import core.tools.ticksToSeconds -import org.json.simple.JSONObject - -@PlayerState("shooting-star") -class ShootingStarState(player: Player? = null) : State(player) { - var ticksLeft = 1500 - - override fun save(root: JSONObject) { - root["ticksLeft"] = ticksLeft - } - - override fun parse(_data: JSONObject) { - if(_data.containsKey("ticksLeft")){ - ticksLeft = _data["ticksLeft"].toString().toInt() - } - } - - override fun newInstance(player: Player?): State { - return ShootingStarState(player) - } - - override fun createPulse() { - player ?: return - if(ticksLeft <= 0) return - pulse = object : Pulse(){ - override fun pulse(): Boolean { - val minutes = ticksToSeconds(ticksLeft) / 60.0 - if(minutes % 5.0 == 0.0){ - player.sendMessage("You have $minutes minutes of your mining bonus left") - } - if(ticksLeft-- <= 0){ - player.sendMessage("Your mining bonus has run out!") - pulse = null - return true - } - return false - } - } - } - -} \ No newline at end of file diff --git a/Server/src/main/content/global/activity/shootingstar/StarBonus.kt b/Server/src/main/content/global/activity/shootingstar/StarBonus.kt new file mode 100644 index 000000000..d0c764e7d --- /dev/null +++ b/Server/src/main/content/global/activity/shootingstar/StarBonus.kt @@ -0,0 +1,28 @@ +package content.global.activity.shootingstar + +import core.api.* +import core.game.system.timer.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import org.json.simple.* + +class StarBonus : PersistTimer (1, "shootingstar:bonus") { + var ticksLeft = 1500 + + override fun save (root: JSONObject, entity: Entity) { + root["ticksLeft"] = ticksLeft.toString() + } + + override fun parse (root: JSONObject, entity: Entity) { + ticksLeft = root["ticksLeft"].toString().toInt() + } + + override fun run (entity: Entity) : Boolean { + if (entity is Player && ticksLeft == 500) { + entity.sendMessage("You have 5 minutes of your mining bonus left") + } else if (entity is Player && ticksLeft == 0) { + entity.sendMessage("Your mining bonus has run out!") + } + return ticksLeft-- > 0 + } +} diff --git a/Server/src/main/content/global/activity/shootingstar/StarSpriteDialogue.kt b/Server/src/main/content/global/activity/shootingstar/StarSpriteDialogue.kt index c1a1e6a75..56b7e1554 100644 --- a/Server/src/main/content/global/activity/shootingstar/StarSpriteDialogue.kt +++ b/Server/src/main/content/global/activity/shootingstar/StarSpriteDialogue.kt @@ -198,7 +198,8 @@ class StarSpriteDialogue(player: Player? = null) : core.game.dialogue.DialoguePl getStoreFile()[player.username.toLowerCase()] = true //flag daily as completed - player.registerState("shooting-star")?.init() + val timer = getOrStartTimer (player) + timer.ticksLeft = 1500 if(wearingRing){ val item = intArrayOf(Items.COSMIC_RUNE_564, Items.ASTRAL_RUNE_9075, Items.GOLD_ORE_445, Items.COINS_995).random() @@ -263,8 +264,8 @@ class StarSpriteDialogue(player: Player? = null) : core.game.dialogue.DialoguePl fun rollForRingBonus(player: Player, bonusId: Int, bonusBaseAmt: Int){ if(RandomFunction.roll(3)){ - val state = player.states["shooting-star"] as? ShootingStarState ?: return - state.ticksLeft += secondsToTicks(TimeUnit.MINUTES.toSeconds(5).toInt()) + var bonus = getOrStartTimer (player) + bonus.ticksLeft += 500 sendMessage(player, colorize("%RYour ring shines dimly as if imbued with energy.")) } else if(RandomFunction.roll(5)){ addItem(player, bonusId, bonusBaseAmt) diff --git a/Server/src/main/content/global/handlers/item/KeldagrimVotingBond.java b/Server/src/main/content/global/handlers/item/KeldagrimVotingBond.java deleted file mode 100644 index dd4612ee0..000000000 --- a/Server/src/main/content/global/handlers/item/KeldagrimVotingBond.java +++ /dev/null @@ -1,188 +0,0 @@ -//package core.game.interaction.item; -// -//import java.text.DecimalFormat; -//import java.util.concurrent.TimeUnit; -// -//import core.cache.def.impl.ItemDefinition; -//import core.game.dialogue.DialogueInterpreter; -//import core.game.dialogue.DialoguePlugin; -//import core.game.content.ttrail.ClueLevel; -//import core.game.content.ttrail.ClueScrollPlugin; -//import core.game.interaction.OptionHandler; -//import core.game.node.Node; -//import core.game.node.entity.player.Player; -//import core.game.node.entity.state.EntityState; -//import core.game.node.item.Item; -//import core.game.world.repository.Repository; -//import core.plugin.Plugin; -//import core.plugin.PluginManager; -//import core.plugin.InitializablePlugin; -//import core.tools.RandomFunction; -// -///** -// * Handles the keldagrim voting bond item. -// * @author Vexia -// * -// */ -//@InitializablePlugin -//public class KeldagrimVotingBond extends OptionHandler { -// -// /** -// * The keldagrim bond item. -// */ -// private static final Item BOND = new Item(14807); -// -// /** -// * The ultra lamp item. -// */ -// private static final Item ULTRA_LAMP = new Item(14820); -// -// @Override -// public Plugin newInstance(Object arg) throws Throwable { -// ItemDefinition.forId(14807).getConfigurations().put("option:redeem", this); -// ItemDefinition.forId(14807).getConfigurations().put("option:deposit", this); -// PluginManager.definePlugin(new keldagrimVotingBondDialogue()); -// return this; -// } -// -// @Override -// public boolean handle(Player player, Node node, String option) { -// Item item = node.asItem(); -// switch (option) { -// case "redeem": -// player.getDialogueInterpreter().open(DialogueInterpreter.getDialogueKey("keldagrim-bond")); -// break; -// case "deposit": -// if (!player.getBank().hasSpaceFor(item)) { -// player.sendMessage("You don't have enough space in your bank for that item."); -// return true; -// } -// if (player.getInventory().remove(item)) { -// player.getBank().add(item); -// player.sendMessage("You deposit your Reward bond into your bank."); -// } -// break; -// } -// return true; -// } -// -// @Override -// public boolean isWalk() { -// return false; -// } -// -// /** -// * Handles the keldagrim voting bond dialogue. -// * @author Vexia -// * -// */ -// public class keldagrimVotingBondDialogue extends DialoguePlugin { -// -// /** -// * Constructs the {@code keldagrimVotingBondDialogue} -// */ -// public keldagrimVotingBondDialogue() { -// /** -// * empty. -// */ -// } -// -// /** -// * Constructs the {@code keldagrimVotingBondDialogue} -// * @param player The player. -// */ -// public keldagrimVotingBondDialogue(Player player) { -// super(player); -// } -// -// @Override -// public DialoguePlugin newInstance(Player player) { -// return new keldagrimVotingBondDialogue(player); -// } -// -// @Override -// public boolean open(Object... args) { -// options("Double Experience (1 Hour)", "30K Experience Lamp", "10k-75k Coins", "Clue Scroll"); -// stage = 0; -// return true; -// } -// -// @Override -// public boolean handle(int interfaceId, int buttonId) { -// switch (stage) { -// case 0: -// stage = 1; -// switch (buttonId) { -// case 1: -// if (player.getSavedData().getGlobalData().hasDoubleExp()) { -// interpreter.sendItemMessage(14807, "You already have double EXP active!"); -// return true; -// } -// if (player.getInventory().remove(BOND)) { -// player.getSavedData().getGlobalData().setDoubleExp(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)); -// player.getStateManager().set(EntityState.DOUBLE_EXPERIENCE, 6000, 0); -// interpreter.sendItemMessage(14807, "You redeemed an hour of double EXP!"); -// Repository.sendNews("" + player.getUsername() + " redeemed an hour of double EXP from a Reward bond!", 15, ""); -// } -// break; -// case 2: -// if (!player.getInventory().hasSpaceFor(ULTRA_LAMP)) { -// interpreter.sendItemMessage(14807, "Sorry, you don't have enough inventory space."); -// return true; -// } -// if (player.getInventory().remove(BOND)) { -// player.getInventory().add(ULTRA_LAMP); -// interpreter.sendItemMessage(14807, "You redeem an ultra lamp."); -// Repository.sendNews("" + player.getUsername() + " redeemed an ultra lamp from a Reward bond!", 15, ""); -// return true; -// } -// break; -// case 3: -// Item coins = new Item(995, RandomFunction.random(10000, 75000)); -// if (!player.getInventory().hasSpaceFor(coins)) { -// interpreter.sendItemMessage(14807, "Sorry, you don't have enough inventory space."); -// return true; -// } -// if (player.getInventory().remove(BOND)) { -// DecimalFormat formatter = new DecimalFormat("#,###"); -// player.getInventory().add(coins); -// interpreter.sendItemMessage(14807, "You redeem " + formatter.format(coins.getAmount()) + " gold coins."); -// Repository.sendNews("" + player.getUsername() + " redeemed " + formatter.format(coins.getAmount()) + " gold coins from a Reward bond!", 15, ""); -// } -// break; -// case 4: -// if (player.getInventory().freeSlots() < 1) { -// interpreter.sendItemMessage(14807, "Sorry, you don't have enough inventory space."); -// return true; -// } -// if (TreasureTrailManager.getInstance(player).hasClue()) { -// interpreter.sendItemMessage(14807, "Sorry, you already have a clue scroll."); -// break; -// } -// if (TreasureTrailManager.getInstance(player).hasTrail()) { -// TreasureTrailManager.getInstance(player).clearTrail(); -// } -// Item clue = ClueScrollPlugin.getClue(RandomFunction.getRandomElement(ClueLevel.values())); -// if (player.getInventory().remove(BOND)) { -// player.getInventory().add(clue); -// interpreter.sendItemMessage(14807, "You redeem a clue scroll."); -// Repository.sendNews("" + player.getUsername() + " redeemed a clue scroll from a Reward bond!", 15, ""); -// } -// break; -// } -// break; -// case 1: -// end(); -// break; -// } -// return true; -// } -// -// @Override -// public int[] getIds() { -// return new int[] {DialogueInterpreter.getDialogueKey("keldagrim-bond")}; -// } -// -// } -// -//} diff --git a/Server/src/main/content/global/handlers/item/equipment/special/DragonfireSwingHandler.java b/Server/src/main/content/global/handlers/item/equipment/special/DragonfireSwingHandler.java index 68f32b8a9..d4b666c78 100644 --- a/Server/src/main/content/global/handlers/item/equipment/special/DragonfireSwingHandler.java +++ b/Server/src/main/content/global/handlers/item/equipment/special/DragonfireSwingHandler.java @@ -10,14 +10,13 @@ import core.game.node.entity.combat.equipment.SwitchAttack; import core.game.node.entity.impl.Projectile; import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.node.item.Item; import core.game.world.GameWorld; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.tools.RandomFunction; -import static core.api.ContentAPIKt.calculateDragonfireMaxHit; +import static core.api.ContentAPIKt.*; /** * Handles dragonfire combat. @@ -147,8 +146,8 @@ public class DragonfireSwingHandler extends CombatSwingHandler { return; } } - if (!fire && victim.getAttribute("freeze_immunity", -1) < GameWorld.getTicks() && RandomFunction.random(4) == 2) { - victim.getStateManager().set(EntityState.FROZEN, 16); + if (!fire && !hasTimerActive(victim, "frozen:immunity") && RandomFunction.random(4) == 2) { + registerTimer(victim, spawnTimer("frozen", 16, true)); victim.graphics(Graphics.create(502)); } Graphics graphic = attack != null ? attack.getEndGraphic() : null; diff --git a/Server/src/main/content/global/handlers/item/equipment/special/ExcaliburSpecialHandler.java b/Server/src/main/content/global/handlers/item/equipment/special/ExcaliburSpecialHandler.java index 96357d23a..018a88f17 100644 --- a/Server/src/main/content/global/handlers/item/equipment/special/ExcaliburSpecialHandler.java +++ b/Server/src/main/content/global/handlers/item/equipment/special/ExcaliburSpecialHandler.java @@ -5,7 +5,6 @@ import core.game.node.entity.skill.Skills; import core.game.node.entity.Entity; import core.game.node.entity.combat.BattleState; import core.game.node.entity.combat.CombatStyle; -import core.game.node.entity.state.EntityState; import core.game.node.entity.combat.MeleeSwingHandler; import core.game.node.entity.impl.Animator.Priority; import core.game.node.entity.player.Player; @@ -14,6 +13,8 @@ import core.game.world.update.flag.context.Graphics; import core.plugin.Initializable; import core.plugin.Plugin; +import static core.api.ContentAPIKt.*; + /** * Handles the excalibur special attack. * @@ -67,7 +68,7 @@ public final class ExcaliburSpecialHandler extends MeleeSwingHandler implements p.getSkills().updateLevel(Skills.DEFENCE, 8, p.getSkills().getStaticLevel(Skills.DEFENCE) + 8); break; case 14632: // enhanced excalibur - p.getStateManager().set(EntityState.HEALOVERTIME,p,(int)15,(int)20,(int)5); + registerTimer(p, spawnTimer("healovertime", 3, 20, 4)); p.getSkills().updateLevel(Skills.DEFENCE, (int)(p.getSkills().getStaticLevel(Skills.DEFENCE)*0.15), (int)(p.getSkills().getStaticLevel(Skills.DEFENCE)*1.15)); diff --git a/Server/src/main/content/global/handlers/item/equipment/special/IceCleaveSpecialHandler.java b/Server/src/main/content/global/handlers/item/equipment/special/IceCleaveSpecialHandler.java index e2db7e221..f988376e4 100644 --- a/Server/src/main/content/global/handlers/item/equipment/special/IceCleaveSpecialHandler.java +++ b/Server/src/main/content/global/handlers/item/equipment/special/IceCleaveSpecialHandler.java @@ -6,13 +6,14 @@ import core.game.node.entity.combat.CombatStyle; import core.game.node.entity.combat.MeleeSwingHandler; import core.game.node.entity.impl.Animator.Priority; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.plugin.Plugin; import core.plugin.Initializable; import core.tools.RandomFunction; +import static core.api.ContentAPIKt.*; + /** * Handles the Ice cleave special attack. * @author Emperor @@ -67,7 +68,7 @@ public final class IceCleaveSpecialHandler extends MeleeSwingHandler implements public void adjustBattleState(Entity entity, Entity victim, BattleState state) { super.adjustBattleState(entity, victim, state); if (state.getEstimatedHit() > 0) { - victim.getStateManager().set(EntityState.FROZEN, 33); + registerTimer(victim, spawnTimer("frozen", 33, true)); victim.graphics(Graphics.create(369)); } } diff --git a/Server/src/main/content/global/handlers/item/equipment/special/ShoveSpecialHandler.java b/Server/src/main/content/global/handlers/item/equipment/special/ShoveSpecialHandler.java index 104a14cf5..63e11b348 100644 --- a/Server/src/main/content/global/handlers/item/equipment/special/ShoveSpecialHandler.java +++ b/Server/src/main/content/global/handlers/item/equipment/special/ShoveSpecialHandler.java @@ -6,7 +6,6 @@ import core.game.node.entity.combat.CombatStyle; import core.game.node.entity.combat.MeleeSwingHandler; import core.game.node.entity.impl.Animator.Priority; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.world.map.Direction; import core.game.world.map.Location; import core.game.world.map.Point; diff --git a/Server/src/main/content/global/handlers/item/withobject/IncubatorPlugin.java b/Server/src/main/content/global/handlers/item/withobject/IncubatorPlugin.java deleted file mode 100644 index 52c3b20da..000000000 --- a/Server/src/main/content/global/handlers/item/withobject/IncubatorPlugin.java +++ /dev/null @@ -1,124 +0,0 @@ -package content.global.handlers.item.withobject; - -import core.cache.def.impl.SceneryDefinition; -import core.game.interaction.NodeUsageEvent; -import core.game.interaction.OptionHandler; -import core.game.interaction.UseWithHandler; -import core.game.node.Node; -import core.game.node.entity.player.Player; -import core.game.node.entity.skill.Skills; -import content.global.skill.summoning.pet.IncubatorEgg; -import core.game.node.item.GroundItemManager; -import core.plugin.Initializable; -import core.plugin.Plugin; -import core.tools.StringUtils; -import content.global.skill.summoning.pet.IncubatorState; -import core.plugin.ClassScanner; - -import static core.api.ContentAPIKt.*; - -/** - * Handles the incubator. - * @author Vexia - */ -@Initializable -public class IncubatorPlugin extends OptionHandler { - - @Override - public Plugin newInstance(Object arg) throws Throwable { - ClassScanner.definePlugin(new IncubatorEggHandler()); - SceneryDefinition.forId(28359).getHandlers().put("option:take-egg", this); - SceneryDefinition.forId(28359).getHandlers().put("option:inspect", this); - return this; - } - - @Override - public boolean handle(Player player, Node node, String option) { - int inc = player.getAttribute(IncubatorState.ATTR_INCUBATOR_PRODUCT, -1); - if (inc == -1) - inc = player.getAttribute("inc", -1); //deprecated, only here to avoid data loss - switch (option) { - case "take-egg": - if (inc == -1) { - player.sendMessage("The egg is still incubating."); - return true; - } - IncubatorEgg egg = IncubatorEgg.values()[inc]; - if (!player.getInventory().hasSpaceFor(egg.getProduct())) { - player.sendMessage("You don't have enough inventory space."); - return true; - } - { - String name = egg.getProduct().getName().toLowerCase(); - setVarbit(player, 4277, 0); - player.sendMessage("You take your " + name + " out of the incubator."); - if(!player.getInventory().add(egg.getProduct())){ - GroundItemManager.create(egg.getProduct(),player); - } - player.removeAttribute("inc"); - player.removeAttribute(IncubatorState.ATTR_INCUBATOR_PRODUCT); - player.clearState("incubator"); - } - return true; - case "inspect": - if (player.states.get("incubator") != null || inc != -1) { - IncubatorState p = (IncubatorState) player.states.get("incubator"); - if(p != null && p.getPulse() == null){ - setVarbit(player, 4277, 0); - return true; - } - String name = p == null ? IncubatorEgg.values()[inc].getProduct().getName().toLowerCase() : p.getEgg().getProduct().getName().toLowerCase(); - player.sendMessage("There is " + (StringUtils.isPlusN(name) ? "an" : "a") + " " + name + " incubating in there."); - } - return true; - } - return true; - } - - /** - * Handles the reward of using an egg on the incubator. - * @author Vexia - */ - public class IncubatorEggHandler extends UseWithHandler { - - /** - * Constructs a new {@Code IncubatorEggHandler} {@Code - * Object} - */ - public IncubatorEggHandler() { - super(12483, 11964, 5077, 5076, 5078, 12494, 12477, 12480, 12478, 12479); - } - - @Override - public Plugin newInstance(Object arg) throws Throwable { - addHandler(28336, OBJECT_TYPE, this); - addHandler(28352, OBJECT_TYPE, this); - return this; - } - - @Override - public boolean handle(NodeUsageEvent event) { - final Player player = event.getPlayer(); - final IncubatorEgg egg = IncubatorEgg.forItem(event.getUsedItem()); - if (egg == null) { - return false; - } - if (player.hasActiveState("incubator")) { - player.sendMessage("You already have an egg in there."); - return true; - } - if (player.getSkills().getStaticLevel(Skills.SUMMONING) < egg.getLevel()) { - player.getDialogueInterpreter().sendDialogue("You need a Summoning level of at least " + egg.getLevel() + " in order to do this."); - return true; - } - if(player.getInventory().remove(egg.getEgg())) { - IncubatorState state = (IncubatorState) player.registerState("incubator"); - state.setEgg(egg); - state.setTicksLeft(egg.getInucbationTime() * 100); - state.init(); - } - return true; - } - - } -} diff --git a/Server/src/main/content/global/skill/farming/CompostBin.kt b/Server/src/main/content/global/skill/farming/CompostBin.kt index b6d38185b..ebaf9a732 100644 --- a/Server/src/main/content/global/skill/farming/CompostBin.kt +++ b/Server/src/main/content/global/skill/farming/CompostBin.kt @@ -54,6 +54,10 @@ class CompostBin(val player: Player, val bin: CompostBins) { return Item(item) } + fun isDefaultState() : Boolean { + return (isFinished == false && finishedTime == 0L && items.size == 0) + } + fun isReady(): Boolean { return System.currentTimeMillis() > finishedTime && finishedTime != 0L } diff --git a/Server/src/main/content/global/skill/farming/CompostBins.kt b/Server/src/main/content/global/skill/farming/CompostBins.kt index e8644b70b..15e4443b4 100644 --- a/Server/src/main/content/global/skill/farming/CompostBins.kt +++ b/Server/src/main/content/global/skill/farming/CompostBins.kt @@ -1,9 +1,11 @@ package content.global.skill.farming +import core.api.* import core.cache.def.impl.SceneryDefinition import core.cache.def.impl.VarbitDefinition import core.game.node.scenery.Scenery import core.game.node.entity.player.Player +import content.global.skill.farming.timers.* enum class CompostBins(val varbit: Int) { FALADOR_COMPOST(740), @@ -28,11 +30,7 @@ enum class CompostBins(val varbit: Int) { } fun getBinForPlayer(player: Player) : CompostBin { - var state: FarmingState? = player.states.get("farming") as FarmingState? - return if(state == null){ - state = player.registerState("farming") as FarmingState - state.getBin(this).also { state.init() } - } else - state.getBin(this) + val bins = getOrStartTimer (player) + return bins.getBin (this) } } diff --git a/Server/src/main/content/global/skill/farming/FarmingPatch.kt b/Server/src/main/content/global/skill/farming/FarmingPatch.kt index e6ce50d97..e32d2b43f 100644 --- a/Server/src/main/content/global/skill/farming/FarmingPatch.kt +++ b/Server/src/main/content/global/skill/farming/FarmingPatch.kt @@ -1,9 +1,11 @@ package content.global.skill.farming +import core.api.* import core.cache.def.impl.SceneryDefinition import core.cache.def.impl.VarbitDefinition import core.game.node.scenery.Scenery import core.game.node.entity.player.Player +import content.global.skill.farming.timers.CropGrowth enum class FarmingPatch(val varbit: Int, val type: PatchType) { //Allotments @@ -86,11 +88,7 @@ enum class FarmingPatch(val varbit: Int, val type: PatchType) { } fun getPatchFor(player: Player): Patch{ - var state: FarmingState? = player.states.get("farming") as FarmingState? - return if(state == null){ - state = player.registerState("farming") as FarmingState - state.getPatch(this).also { state.init() } - } else - state.getPatch(this) + var crops = getOrStartTimer (player)!! + return crops.getPatch(this) } } diff --git a/Server/src/main/content/global/skill/farming/FarmingState.kt b/Server/src/main/content/global/skill/farming/FarmingState.kt index f6da3da8e..ed7f769e5 100644 --- a/Server/src/main/content/global/skill/farming/FarmingState.kt +++ b/Server/src/main/content/global/skill/farming/FarmingState.kt @@ -1,5 +1,6 @@ package content.global.skill.farming +import core.api.* import core.Util.clamp import core.game.node.entity.player.Player import core.game.system.task.Pulse @@ -11,125 +12,26 @@ import core.game.node.entity.state.PlayerState import core.game.node.entity.state.State import core.tools.SystemLogger import java.util.concurrent.TimeUnit +import content.global.skill.farming.timers.* @PlayerState("farming") +/** + * Kept around solely for the purpose of porting save data from this old system to the new one. + * //TODO REMOVE BY END OF 2023 +**/ class FarmingState(player: Player? = null) : State(player) { - private val patchMap = HashMap() - private val binMap = HashMap() - - - fun getPatch(patch: FarmingPatch): Patch { - return patchMap[patch] ?: (Patch(player!!,patch).also { patchMap[patch] = it }) - } - - fun getBin(bin: CompostBins) : CompostBin{ - return binMap[bin] ?: (CompostBin(player!!,bin).also { binMap[bin] = it }) - } - - fun getPatches(): MutableCollection{ - return patchMap.values - } - - fun getBins(): MutableCollection{ - return binMap.values - } - - override fun save(root: JSONObject) { - val patches = JSONArray() - for((key,patch) in patchMap){ - val p = JSONObject() - p.put("patch-ordinal",key.ordinal) - p.put("patch-plantable-ordinal",patch.plantable?.ordinal ?: -1) - p.put("patch-watered",patch.isWatered) - p.put("patch-diseased",patch.isDiseased) - p.put("patch-dead",patch.isDead) - p.put("patch-stage",patch.currentGrowthStage) - p.put("patch-state",patch.getCurrentState()) - p.put("patch-nextGrowth",patch.nextGrowth) - p.put("patch-harvestAmt",patch.harvestAmt) - p.put("patch-checkHealth",patch.isCheckHealth) - p.put("patch-compost",patch.compost.ordinal) - p.put("patch-paidprot",patch.protectionPaid) - p.put("patch-croplives", patch.cropLives) - patches.add(p) - } - val bins = JSONArray() - for((key,bin) in binMap){ - val b = JSONObject() - b.put("bin-ordinal",key.ordinal) - bin.save(b) - bins.add(b) - } - root.put("farming-patches",patches) - root.put("farming-bins",bins) - } - + override fun save(root: JSONObject) {} override fun parse(_data: JSONObject) { player ?: return if(_data.containsKey("farming-bins")){ - (_data["farming-bins"] as JSONArray).forEach { - val bin = it as JSONObject - val binOrdinal = bin["bin-ordinal"].toString().toInt() - val cBin = CompostBins.values()[binOrdinal] - val b = cBin.getBinForPlayer(player) - b.parse(bin["binData"] as JSONObject) - } + _data["bins"] = _data["farming-bins"] + val timer = getOrStartTimer (player) + timer.parse (_data, player) } if(_data.containsKey("farming-patches")){ - val data = _data["farming-patches"] as JSONArray - for(d in data){ - val p = d as JSONObject - val patchOrdinal = p["patch-ordinal"].toString().toInt() - val patchPlantableOrdinal = p["patch-plantable-ordinal"].toString().toInt() - val patchWatered = p["patch-watered"] as Boolean - val patchDiseased = p["patch-diseased"] as Boolean - val patchDead = p["patch-dead"] as Boolean - val patchStage = p["patch-stage"].toString().toInt() - val nextGrowth = p["patch-nextGrowth"].toString().toLong() - val harvestAmt = (p["patch-harvestAmt"] ?: 0).toString().toInt() - val checkHealth = p["patch-checkHealth"] as Boolean - val savedState = p["patch-state"].toString().toInt() - val compostOrdinal = p["patch-compost"].toString().toInt() - val protectionPaid = p["patch-paidprot"] as Boolean - val cropLives = if(p["patch-croplives"] != null) p["patch-croplives"].toString().toInt() else 3 - val fPatch = FarmingPatch.values()[patchOrdinal] - val plantable = if(patchPlantableOrdinal != -1) Plantable.values()[patchPlantableOrdinal] else null - val patch = Patch(player,fPatch,plantable,patchStage,patchDiseased,patchDead,patchWatered,nextGrowth,harvestAmt,checkHealth) - - patch.cropLives = cropLives - patch.compost = CompostType.values()[compostOrdinal] - patch.protectionPaid = protectionPaid - patch.setCurrentState(savedState) - - if((savedState - (patch?.plantable?.value ?: 0)) > patch.currentGrowthStage){ - patch.setCurrentState(savedState) - } else { - patch.setCurrentState((patch.plantable?.value ?: 0) + patch.currentGrowthStage) - } - - val type = patch.patch.type - val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE && patch.getFruitOrBerryCount() < 6) - if(shouldPlayCatchup && patch.plantable != null && !patchDead){ - var stagesToSimulate = if (!patch.isGrown()) patch.plantable!!.stages - patch.currentGrowthStage else 0 - if (type == PatchType.BUSH) - stagesToSimulate += Math.min(4, 4 - patch.getFruitOrBerryCount()) - if (type == PatchType.FRUIT_TREE) - stagesToSimulate += Math.min(6, 6 - patch.getFruitOrBerryCount()) - - val nowTime = System.currentTimeMillis() - var simulatedTime = patch.nextGrowth - - while (simulatedTime < nowTime && stagesToSimulate-- > 0 && !patch.isDead) { - val timeToIncrement = TimeUnit.MINUTES.toMillis(patch.getStageGrowthMinutes().toLong()) - patch.update() - simulatedTime += timeToIncrement - } - } - - if(patchMap[fPatch] == null) { - patchMap[fPatch] = patch - } - } + _data["patches"] = _data["farming-patches"] + val timer = getOrStartTimer (player) + timer.parse(_data, player) } } @@ -137,35 +39,5 @@ class FarmingState(player: Player? = null) : State(player) { return FarmingState(player) } - override fun createPulse() { - pulse = object : Pulse(3){ - override fun pulse(): Boolean { - - GlobalScope.launch { - var removeList = ArrayList() - for((_,patch) in patchMap){ - - if(patch.getCurrentState() in 1..3 && patch.nextGrowth == 0L){ - patch.nextGrowth = System.currentTimeMillis() + 60000 - continue - } - - if(patch.nextGrowth < System.currentTimeMillis() && !patch.isDead){ - patch.update() - patch.nextGrowth = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(patch.getStageGrowthMinutes().toLong()) - } - - } - - for((_,bin) in binMap){ - if(bin.isReady() && !bin.isFinished){ - bin.finish() - } - } - } - - return false - } - } - } + override fun createPulse() {} } diff --git a/Server/src/main/content/global/skill/farming/PatchType.kt b/Server/src/main/content/global/skill/farming/PatchType.kt index 20a76ee97..97c42fdf7 100644 --- a/Server/src/main/content/global/skill/farming/PatchType.kt +++ b/Server/src/main/content/global/skill/farming/PatchType.kt @@ -8,7 +8,7 @@ enum class PatchType(val stageGrowthTime: Int) { BUSH(20), FLOWER(5), HERB(20), - SPIRIT_TREE(293), + SPIRIT_TREE(295), MUSHROOM(30), BELLADONNA(80), CACTUS(60), diff --git a/Server/src/main/content/global/skill/farming/SeedOnPlantPot.kt b/Server/src/main/content/global/skill/farming/SeedOnPlantPot.kt index 42d921dd9..170c986c2 100644 --- a/Server/src/main/content/global/skill/farming/SeedOnPlantPot.kt +++ b/Server/src/main/content/global/skill/farming/SeedOnPlantPot.kt @@ -1,13 +1,11 @@ package content.global.skill.farming -import core.api.addItem -import core.api.inInventory -import core.api.removeItem -import core.api.sendDialogue +import core.api.* import core.game.node.Node -import core.game.node.entity.player.Player import org.rs09.consts.Items import core.game.interaction.IntType +import core.game.node.entity.player.Player +import content.global.skill.farming.timers.* import core.game.interaction.InteractionListener class SeedlingListener : InteractionListener { @@ -43,17 +41,8 @@ class SeedlingListener : InteractionListener { addItem(player, wateredSeedling) addItem(player, nextCan) - var state = player.states["seedling"] as SeedlingState? - - if (state != null) { - state.addSeedling(wateredSeedling) - return true - } - - state = player.registerState("seedling") as SeedlingState? - state?.addSeedling(wateredSeedling) - state?.init() - + var seedlings = getOrStartTimer (player) + seedlings.addSeedling(wateredSeedling) return true } @@ -114,4 +103,4 @@ class SeedlingListener : InteractionListener { ) private val WATERING_CANS = intArrayOf(Items.WATERING_CAN8_5340,Items.WATERING_CAN7_5339,Items.WATERING_CAN6_5338,Items.WATERING_CAN5_5337,Items.WATERING_CAN4_5336,Items.WATERING_CAN3_5335,Items.WATERING_CAN2_5334,Items.WATERING_CAN1_5333) -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/skill/farming/SeedlingState.kt b/Server/src/main/content/global/skill/farming/SeedlingState.kt deleted file mode 100644 index 8fb2877f0..000000000 --- a/Server/src/main/content/global/skill/farming/SeedlingState.kt +++ /dev/null @@ -1,83 +0,0 @@ -package content.global.skill.farming - -import core.game.node.entity.player.Player -import core.game.node.item.Item -import core.game.system.task.Pulse -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.json.simple.JSONArray -import org.json.simple.JSONObject -import core.game.node.entity.state.PlayerState -import core.game.node.entity.state.State -import java.util.concurrent.TimeUnit - -@PlayerState("seedling") -class SeedlingState(player: Player? = null) : State(player) { - val seedlings = ArrayList() - - fun addSeedling(seedling: Int){ - seedlings.add(Seedling(seedling, System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5),seedling + if(seedling > 5400) 8 else 6)) - } - - override fun save(root: JSONObject) { - val seedArray = JSONArray() - for(s in seedlings){ - val seed = JSONObject() - seed.put("id",s.id) - seed.put("ttl",s.TTL) - seed.put("sapling",s.sapling) - seedArray.add(seed) - } - root.put("seedlings",seedArray) - } - - override fun parse(_data: JSONObject) { - if(_data.containsKey("seedlings")){ - (_data["seedlings"] as JSONArray).forEach { - val s = it as JSONObject - val id = s["id"].toString().toInt() - val ttl = s["ttl"].toString().toLong() - val sapling = s["sapling"].toString().toInt() - seedlings.add(Seedling(id,ttl,sapling)) - } - } - } - - override fun newInstance(player: Player?): State { - return SeedlingState(player) - } - - override fun createPulse() { - if(seedlings.isEmpty()) return - player ?: return - - pulse = object : Pulse(5){ - override fun pulse(): Boolean { - val removeList = ArrayList() - GlobalScope.launch { - for (seed in seedlings) { - if (System.currentTimeMillis() > seed.TTL) { - val inInventory = player.inventory.get(Item(seed.id)) - if (inInventory != null) { - player.inventory.replace(Item(seed.sapling), inInventory.slot) - removeList.add(seed) - } else { - val inBank = player.bank.get(Item(seed.id)) - if(inBank == null) removeList.add(seed) - else { - player.bank.remove(Item(inBank.id,1)) - player.bank.add(Item(seed.sapling)) - removeList.add(seed) - } - } - } - } - seedlings.removeAll(removeList) - } - return false - } - } - - } - -} \ No newline at end of file diff --git a/Server/src/main/content/global/skill/farming/timers/Compost.kt b/Server/src/main/content/global/skill/farming/timers/Compost.kt new file mode 100644 index 000000000..cc69490a0 --- /dev/null +++ b/Server/src/main/content/global/skill/farming/timers/Compost.kt @@ -0,0 +1,65 @@ +package content.global.skill.farming.timers + +import core.api.* +import core.game.system.timer.* +import org.json.simple.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import content.global.skill.farming.* + +class Compost : PersistTimer (500, "farming:compost", isSoft = true) { + private val binMap = HashMap() + lateinit var player: Player + + override fun onRegister (entity: Entity) { + player = (entity as? Player)!! + } + + override fun getInitialRunDelay() : Int { + return 1 //run once immediately after log in to complete any pending-but-enough-time-has-passed bins. + } + + override fun run (entity: Entity) : Boolean { + val removeList = ArrayList () + for((cBin,bin) in binMap){ + if(bin.isReady() && !bin.isFinished){ + bin.finish() + } + else if (bin.isDefaultState()) + removeList.add(cBin) + } + removeList.forEach { binMap.remove(it) } + removeList.clear() + + return binMap.isNotEmpty() + } + + fun getBin (bin: CompostBins) : CompostBin{ + return binMap[bin] ?: (CompostBin (player, bin).also { binMap[bin] = it }) + } + + fun getBins(): MutableCollection{ + return binMap.values + } + + override fun save (root: JSONObject, entity: Entity) { + val bins = JSONArray() + for((key,bin) in binMap){ + val b = JSONObject() + b.put("bin-ordinal",key.ordinal) + bin.save(b) + bins.add(b) + } + root.put("bins", bins) + } + + override fun parse (root: JSONObject, entity: Entity) { + (root["bins"] as JSONArray).forEach { + val bin = it as JSONObject + val binOrdinal = bin["bin-ordinal"].toString().toInt() + val cBin = CompostBins.values()[binOrdinal] + val b = CompostBin ((entity as? Player)!!, cBin).also { binMap[cBin] = it } + b.parse(bin["binData"] as JSONObject) + } + } +} diff --git a/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt new file mode 100644 index 000000000..9d5dfc90a --- /dev/null +++ b/Server/src/main/content/global/skill/farming/timers/CropGrowth.kt @@ -0,0 +1,148 @@ +package content.global.skill.farming.timers + +import core.api.* +import core.tools.* +import core.game.system.timer.* +import org.json.simple.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import content.global.skill.farming.* +import java.util.concurrent.TimeUnit +import java.time.* + +class CropGrowth : PersistTimer (500, "farming:crops", isSoft = true) { + private val patchMap = HashMap() + lateinit var player: Player + + override fun onRegister (entity: Entity) { + player = (entity as? Player)!! + runOfflineCatchupLogic() + } + + //Sync the 5 minute run cycles with :05 on realtime clocks - authentic + override fun getInitialRunDelay() : Int { + val now = LocalTime.now(); + val minsUntil5MinSync = 5 - (now.getMinute() % 5) + val ticks = secondsToTicks (minsUntil5MinSync * 60) + player.debug("[CropGrowth] Scheduled first growth cycle for $ticks ticks from now.") + return ticks + } + + override fun run (entity: Entity) : Boolean { + var removeList = ArrayList() + for((fp,patch) in patchMap){ + if(patch.getCurrentState() in 1..3 && patch.nextGrowth == 0L){ + patch.nextGrowth = System.currentTimeMillis() + 60000 + continue + } + + //Go ahead and grow anything within 4 minutes of the 5-minute-synced growth cycles, bringing out-of-sync patches into sync. + //This seems to be authentic as well, with the RS wiki sometimes stating 20-minute patches can grow in as little as 7 minutes depending on timing of planting + //It also makes sense, as otherwise if you e.g. planted something at 10:34 that takes 5 minutes to grow, it would take 6 minutes in reality instead of 5. + //Another more extreme example is if you planted something at 10:31 that takes 5 minutes to grow. 10:35 comes around, it hasn't been 5 minutes, so it doesn't grow, meaning + //it actually grows at 10:40, an extra 4 minutes. + //this code makes it so crops planted both at 10:31 and 10:34 grow at 10:35 if they are supposed to take 5 minutes for each stage. + if(patch.nextGrowth < (System.currentTimeMillis() + 240_000L) && !patch.isDead){ + patch.nextGrowth = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(patch.getStageGrowthMinutes().toLong()) + patch.update() + } + + if (patch.getCurrentState() == 0) + removeList.add(fp) + } + removeList.forEach { patchMap.remove(it) } + removeList.clear() + return patchMap.isNotEmpty() + } + + private fun runOfflineCatchupLogic() { + for ((_, patch) in patchMap) { + val type = patch.patch.type + val shouldPlayCatchup = !patch.isGrown() || (type == PatchType.BUSH && patch.getFruitOrBerryCount() < 4) || (type == PatchType.FRUIT_TREE && patch.getFruitOrBerryCount() < 6) + if(shouldPlayCatchup && patch.plantable != null && !patch.isDead){ + var stagesToSimulate = if (!patch.isGrown()) patch.plantable!!.stages - patch.currentGrowthStage else 0 + if (type == PatchType.BUSH) + stagesToSimulate += Math.min(4, 4 - patch.getFruitOrBerryCount()) + if (type == PatchType.FRUIT_TREE) + stagesToSimulate += Math.min(6, 6 - patch.getFruitOrBerryCount()) + + val nowTime = System.currentTimeMillis() + var simulatedTime = patch.nextGrowth + + while (simulatedTime < nowTime && stagesToSimulate-- > 0 && !patch.isDead) { + val timeToIncrement = TimeUnit.MINUTES.toMillis(patch.getStageGrowthMinutes().toLong()) + patch.update() + simulatedTime += timeToIncrement + } + } + } + } + + fun getPatch(patch: FarmingPatch): Patch { + return patchMap[patch] ?: (Patch(player,patch).also { patchMap[patch] = it }) + } + + fun getPatches(): MutableCollection{ + return patchMap.values + } + + override fun save (root: JSONObject, entity: Entity) { + val patches = JSONArray() + for((key,patch) in patchMap){ + val p = JSONObject() + p.put("patch-ordinal",key.ordinal) + p.put("patch-plantable-ordinal",patch.plantable?.ordinal ?: -1) + p.put("patch-watered",patch.isWatered) + p.put("patch-diseased",patch.isDiseased) + p.put("patch-dead",patch.isDead) + p.put("patch-stage",patch.currentGrowthStage) + p.put("patch-state",patch.getCurrentState()) + p.put("patch-nextGrowth",patch.nextGrowth) + p.put("patch-harvestAmt",patch.harvestAmt) + p.put("patch-checkHealth",patch.isCheckHealth) + p.put("patch-compost",patch.compost.ordinal) + p.put("patch-paidprot",patch.protectionPaid) + p.put("patch-croplives", patch.cropLives) + patches.add(p) + } + root["patches"] = patches + } + + override fun parse (root: JSONObject, entity: Entity) { + val data = root["patches"] as JSONArray + for(d in data){ + val p = d as JSONObject + val patchOrdinal = p["patch-ordinal"].toString().toInt() + val patchPlantableOrdinal = p["patch-plantable-ordinal"].toString().toInt() + val patchWatered = p["patch-watered"] as Boolean + val patchDiseased = p["patch-diseased"] as Boolean + val patchDead = p["patch-dead"] as Boolean + val patchStage = p["patch-stage"].toString().toInt() + val nextGrowth = p["patch-nextGrowth"].toString().toLong() + val harvestAmt = (p["patch-harvestAmt"] ?: 0).toString().toInt() + val checkHealth = p["patch-checkHealth"] as Boolean + val savedState = p["patch-state"].toString().toInt() + val compostOrdinal = p["patch-compost"].toString().toInt() + val protectionPaid = p["patch-paidprot"] as Boolean + val cropLives = if(p["patch-croplives"] != null) p["patch-croplives"].toString().toInt() else 3 + val fPatch = FarmingPatch.values()[patchOrdinal] + val plantable = if(patchPlantableOrdinal != -1) Plantable.values()[patchPlantableOrdinal] else null + val patch = Patch((entity as? Player)!!,fPatch,plantable,patchStage,patchDiseased,patchDead,patchWatered,nextGrowth,harvestAmt,checkHealth) + + patch.cropLives = cropLives + patch.compost = CompostType.values()[compostOrdinal] + patch.protectionPaid = protectionPaid + patch.setCurrentState(savedState) + + if((savedState - (patch?.plantable?.value ?: 0)) > patch.currentGrowthStage){ + patch.setCurrentState(savedState) + } else { + patch.setCurrentState((patch.plantable?.value ?: 0) + patch.currentGrowthStage) + } + + if(patchMap[fPatch] == null) { + patchMap[fPatch] = patch + } + } + } +} diff --git a/Server/src/main/content/global/skill/farming/timers/SeedlingGrowth.kt b/Server/src/main/content/global/skill/farming/timers/SeedlingGrowth.kt new file mode 100644 index 000000000..843c6871c --- /dev/null +++ b/Server/src/main/content/global/skill/farming/timers/SeedlingGrowth.kt @@ -0,0 +1,74 @@ +package content.global.skill.farming.timers + +import core.game.node.entity.Entity +import core.game.node.item.Item +import core.game.system.timer.* +import core.game.node.entity.player.Player +import content.global.skill.farming.* +import java.util.concurrent.TimeUnit +import org.json.simple.* +import java.time.* + +class SeedlingGrowth : PersistTimer (1, "farming:seedling", isSoft = true) { + val seedlings = ArrayList() + lateinit var player: Player + + fun addSeedling(seedling: Int){ + seedlings.add( + Seedling( + seedling, + System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5), + seedling + if(seedling > 5400) 8 else 6 + ) + ) + } + + override fun onRegister (entity: Entity) { + player = (entity as? Player)!! + } + + override fun run (entity: Entity) : Boolean { + val removeList = ArrayList() + for (seed in seedlings) { + if (System.currentTimeMillis() > seed.TTL) { + val inInventory = player.inventory.get(Item(seed.id)) + if (inInventory != null) { + player.inventory.replace(Item(seed.sapling), inInventory.slot) + removeList.add(seed) + } else { + val inBank = player.bank.get(Item(seed.id)) + if(inBank == null) removeList.add(seed) + else { + player.bank.remove(Item(inBank.id,1)) + player.bank.add(Item(seed.sapling)) + removeList.add(seed) + } + } + } + } + seedlings.removeAll(removeList) + return seedlings.isNotEmpty() + } + + override fun save(root: JSONObject, entity: Entity) { + val seedArray = JSONArray() + for(s in seedlings){ + val seed = JSONObject() + seed.put("id",s.id) + seed.put("ttl",s.TTL) + seed.put("sapling",s.sapling) + seedArray.add(seed) + } + root.put("seedlings",seedArray) + } + + override fun parse(root: JSONObject, entity: Entity) { + (root["seedlings"] as JSONArray).forEach { + val s = it as JSONObject + val id = s["id"].toString().toInt() + val ttl = s["ttl"].toString().toLong() + val sapling = s["sapling"].toString().toInt() + seedlings.add(Seedling(id,ttl,sapling)) + } + } +} diff --git a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt index b07f318b0..73c252911 100644 --- a/Server/src/main/content/global/skill/gather/mining/MiningListener.kt +++ b/Server/src/main/content/global/skill/gather/mining/MiningListener.kt @@ -3,6 +3,7 @@ package content.global.skill.gather.mining import content.data.skill.SkillingPets import content.data.skill.SkillingTool import content.global.skill.skillcapeperks.SkillcapePerks +import content.global.activity.shootingstar.StarBonus import core.api.* import core.cache.def.impl.ItemDefinition import core.game.event.ResourceProducedEvent @@ -159,7 +160,7 @@ class MiningListener : InteractionListener { } // If player has mining boost from Shooting Star, roll chance at extra ore - if (player.hasActiveState("shooting-star")) { + if (hasTimerActive(player)) { if (RandomFunction.getRandom(5) == 3) { sendMessage(player, "...you manage to mine a second ore thanks to the Star Sprite.") amount += 1 @@ -222,4 +223,4 @@ class MiningListener : InteractionListener { } return node.isActive } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/skill/magic/ancient/IceSpells.java b/Server/src/main/content/global/skill/magic/ancient/IceSpells.java index 1af555d40..43d887426 100644 --- a/Server/src/main/content/global/skill/magic/ancient/IceSpells.java +++ b/Server/src/main/content/global/skill/magic/ancient/IceSpells.java @@ -12,7 +12,6 @@ import core.game.node.entity.impl.Projectile; import core.game.node.entity.impl.Animator.Priority; import core.game.node.entity.player.link.SpellBookManager.SpellBook; import core.game.node.entity.player.link.audio.Audio; -import core.game.node.entity.state.EntityState; import core.game.node.item.Item; import core.game.world.GameWorld; import core.game.world.update.flag.context.Animation; @@ -20,6 +19,8 @@ import core.game.world.update.flag.context.Graphics; import core.plugin.Initializable; import core.plugin.Plugin; +import static core.api.ContentAPIKt.*; + /** * Handles the Ice spells from the Ancient spellbook. * @author Emperor @@ -133,8 +134,8 @@ public final class IceSpells extends CombatSpell { } int ticks = (1 + (type.ordinal() - SpellType.RUSH.ordinal())) * 8; if (state.getEstimatedHit() > -1) { - if (victim.getAttribute("freeze_immunity", -1) < GameWorld.getTicks()) { - victim.getStateManager().set(EntityState.FROZEN, ticks); + if (!hasTimerActive(victim, "frozen:immunity")) { + registerTimer(victim, spawnTimer("frozen", ticks, true)); } else if (type == SpellType.BARRAGE) { state.setFrozen(true); } diff --git a/Server/src/main/content/global/skill/magic/ancient/MiasmicSpells.java b/Server/src/main/content/global/skill/magic/ancient/MiasmicSpells.java index d2c331366..ce74740f0 100644 --- a/Server/src/main/content/global/skill/magic/ancient/MiasmicSpells.java +++ b/Server/src/main/content/global/skill/magic/ancient/MiasmicSpells.java @@ -14,13 +14,14 @@ import core.game.node.entity.impl.Projectile; import core.game.node.entity.impl.Animator.Priority; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.SpellBookManager.SpellBook; -import core.game.node.entity.state.EntityState; import core.game.node.item.Item; import core.game.world.GameWorld; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.plugin.Plugin; +import static core.api.ContentAPIKt.*; + /** * Handles the Miasmic spells that are a part of the Ancient spellbook. * @author Splinter @@ -119,8 +120,8 @@ public final class MiasmicSpells extends CombatSpell { @Override public void fireEffect(Entity entity, Entity victim, BattleState state) { - if (victim.getAttribute("miasmic_immunity", -1) < GameWorld.getTicks()) { - victim.getStateManager().register(EntityState.MIASMIC, true, (getSpellId() - 15) * 20); + if (!hasTimerActive(victim, "miasmic:immunity")) { + registerTimer(victim, spawnTimer("miasmic", (getSpellId() - 15) * 20)); } } diff --git a/Server/src/main/content/global/skill/magic/ancient/SmokeSpells.java b/Server/src/main/content/global/skill/magic/ancient/SmokeSpells.java index b92c9ea02..7c3d23164 100644 --- a/Server/src/main/content/global/skill/magic/ancient/SmokeSpells.java +++ b/Server/src/main/content/global/skill/magic/ancient/SmokeSpells.java @@ -11,13 +11,14 @@ import core.game.node.entity.combat.spell.SpellType; import core.game.node.entity.impl.Projectile; import core.game.node.entity.impl.Animator.Priority; import core.game.node.entity.player.link.SpellBookManager.SpellBook; -import core.game.node.entity.state.EntityState; import core.game.node.item.Item; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.plugin.Initializable; import core.plugin.Plugin; +import static core.api.ContentAPIKt.*; + /** * Handles the Smoke spells from the Ancient spellbook. * @author Emperor @@ -111,7 +112,7 @@ public final class SmokeSpells extends CombatSpell { @Override public void fireEffect(Entity entity, Entity victim, BattleState state) { if (state.getEstimatedHit() > -1) { - victim.getStateManager().register(EntityState.POISONED, false, type.ordinal() >= SpellType.BLITZ.ordinal() ? 48 : 28, entity); + applyPoison(victim, entity, type.ordinal() >= SpellType.BLITZ.ordinal() ? 4 : 2); } } diff --git a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt index d6a67dd90..ba0add9fb 100644 --- a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt +++ b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt @@ -15,7 +15,6 @@ import core.game.node.entity.player.Player import core.game.node.entity.player.link.TeleportManager import core.game.node.entity.player.link.audio.Audio import core.game.node.entity.skill.Skills -import core.game.node.entity.state.EntityState import core.game.node.item.Item import core.game.node.scenery.Scenery import core.game.system.command.Privilege @@ -206,7 +205,7 @@ class LunarListeners : SpellListener("lunar"), Commands { return@define } if(dmg != null) { - p?.let { addState(it, EntityState.POISONED, false, (dmg * 10 + 8), player) } + p?.let { applyPoison(it, it, dmg) } } else { sendMessage(player, "Damage must be an integer. Format:") sendMessage(player, "::poison username damage") @@ -480,14 +479,14 @@ class LunarListeners : SpellListener("lunar"), Commands { } private fun cureMe(player: Player) { - if(!hasState(player, EntityState.POISONED)) { + if(!isPoisoned(player)) { sendMessage(player, "You are not poisoned.") return } requires(player, 71, arrayOf(Item(Items.ASTRAL_RUNE_9075, 2), Item(Items.LAW_RUNE_563, 1), Item(Items.COSMIC_RUNE_564, 2))) removeRunes(player, true) visualizeSpell(player, CURE_ME_ANIM, CURE_ME_GFX, 2880) - removeState(player, EntityState.POISONED) + curePoison(player) addXP(player, 69.0) playAudio(player, Audio(2900)) sendMessage(player, "You have been cured of poison.") @@ -497,7 +496,7 @@ class LunarListeners : SpellListener("lunar"), Commands { requires(player, 74, arrayOf(Item(Items.ASTRAL_RUNE_9075, 2), Item(Items.LAW_RUNE_563, 2), Item(Items.COSMIC_RUNE_564, 2))) removeRunes(player, true) visualizeSpell(player, CURE_GROUP_ANIM, CURE_GROUP_GFX, 2882) - removeState(player, EntityState.POISONED) + curePoison(player) for(acct in RegionManager.getLocalPlayers(player, 1)) { if(!acct.isActive || acct.locks.isInteractionLocked) { continue @@ -505,7 +504,7 @@ class LunarListeners : SpellListener("lunar"), Commands { if(!acct.settings.isAcceptAid) { continue } - removeState(acct, EntityState.POISONED) + curePoison(acct) sendMessage(acct, "You have been cured of poison.") playAudio(acct, Audio(2889), true) visualize(acct, -1, CURE_GROUP_GFX) @@ -527,7 +526,7 @@ class LunarListeners : SpellListener("lunar"), Commands { sendMessage(player, "This player is not accepting any aid.") return } - if(!hasState(p, EntityState.POISONED)) { + if(!isPoisoned(p)) { sendMessage(player, "This player is not poisoned.") return } @@ -537,7 +536,7 @@ class LunarListeners : SpellListener("lunar"), Commands { visualize(p, -1, CURE_OTHER_GFX) playAudio(p, Audio(2889), true) removeRunes(player, true) - removeState(p, EntityState.POISONED) + curePoison(p) sendMessage(p, "You have been cured of poison.") addXP(player, 65.0) } diff --git a/Server/src/main/content/global/skill/magic/modern/ChargeSpell.java b/Server/src/main/content/global/skill/magic/modern/ChargeSpell.java index efa51e673..4aa2b1c1b 100644 --- a/Server/src/main/content/global/skill/magic/modern/ChargeSpell.java +++ b/Server/src/main/content/global/skill/magic/modern/ChargeSpell.java @@ -15,6 +15,8 @@ import core.plugin.Initializable; import core.plugin.Plugin; import org.rs09.consts.Sounds; +import static core.api.ContentAPIKt.*; + /** * Represents the charge spell magic spell. * @author Emperor @@ -49,8 +51,8 @@ public final class ChargeSpell extends MagicSpell { p.getLocks().lock("charge_cast", 100); visualize(entity, target); // Remove the previous copy of the state in order to refresh the duration if recast before 7 minutes - p.clearState("godcharge"); - p.registerState("godcharge").init(); + removeTimer (p, "magic:spellcharge"); + registerTimer (p, spawnTimer("magic:spellcharge")); p.getPacketDispatch().sendMessage("You feel charged with magic power."); return true; } diff --git a/Server/src/main/content/global/skill/magic/modern/GodspellChargedState.kt b/Server/src/main/content/global/skill/magic/modern/GodspellChargedState.kt deleted file mode 100644 index 3e7ef71fc..000000000 --- a/Server/src/main/content/global/skill/magic/modern/GodspellChargedState.kt +++ /dev/null @@ -1,46 +0,0 @@ -package content.global.skill.magic.modern - -import core.game.node.entity.player.Player -import core.game.node.entity.player.link.audio.Audio -import core.game.system.task.Pulse -import org.json.simple.JSONObject -import core.game.node.entity.state.PlayerState -import core.game.node.entity.state.State -import core.game.world.GameWorld; - -@PlayerState("godcharge") -class GodspellChargedState(player: Player? = null) : State(player) { - val DURATION = 700 - var startTick: Int = 0 - - override fun save(root: JSONObject) { - root.put("ticks_elapsed", GameWorld.ticks - startTick) - } - - override fun parse(_data: JSONObject) { - if(_data.containsKey("ticks_elapsed")){ - startTick = GameWorld.ticks - _data["ticks_elapsed"].toString().toInt() - } - } - - override fun newInstance(player: Player?): State { - var ret = GodspellChargedState(player) - ret.startTick = GameWorld.ticks - return ret - } - - override fun createPulse() { - player ?: return - if(GameWorld.ticks - startTick >= DURATION) return - pulse = object : Pulse(DURATION) { - override fun pulse(): Boolean { - player.sendMessage("Your magical charge fades away.") - player.clearState("godcharge") - player.audioManager.send(Audio(1650)) - pulse = null - return true - } - } - } - -} diff --git a/Server/src/main/content/global/skill/magic/modern/SpellCharge.kt b/Server/src/main/content/global/skill/magic/modern/SpellCharge.kt new file mode 100644 index 000000000..2f6dee31d --- /dev/null +++ b/Server/src/main/content/global/skill/magic/modern/SpellCharge.kt @@ -0,0 +1,16 @@ +package content.global.skill.magic.modern + +import core.api.* +import org.json.simple.* +import core.game.system.timer.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player + +class SpellCharge : PersistTimer (700, "magic:spellcharge") { + override fun run (entity: Entity) : Boolean { + if (entity !is Player) return false + sendMessage(entity, "Your magical charge fades away.") + playAudio(entity, getAudio(1650)) + return false + } +} diff --git a/Server/src/main/content/global/skill/magic/modern/TeleblockSpell.java b/Server/src/main/content/global/skill/magic/modern/TeleblockSpell.java index 3f8c0f288..4a08a3476 100644 --- a/Server/src/main/content/global/skill/magic/modern/TeleblockSpell.java +++ b/Server/src/main/content/global/skill/magic/modern/TeleblockSpell.java @@ -11,13 +11,14 @@ import core.game.node.entity.impl.Projectile; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.SpellBookManager.SpellBook; import core.game.node.entity.player.link.prayer.PrayerType; -import core.game.node.entity.state.EntityState; import core.game.node.item.Item; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.plugin.Initializable; import core.plugin.Plugin; +import static core.api.ContentAPIKt.*; + /** * Handles the teleportation block spell in the modern spellbook. * @author Splinter @@ -117,7 +118,7 @@ public final class TeleblockSpell extends CombatSpell { if(((Player) victim).getPrayer().get(PrayerType.PROTECT_FROM_MAGIC)){ ticks /= 2; } - victim.getStateManager().set(EntityState.TELEBLOCK, ticks); + registerTimer(victim, spawnTimer("teleblock", ticks)); } else if(victim.isTeleBlocked()){ entity.asPlayer().sendMessage("Your target is already blocked from teleporting."); } diff --git a/Server/src/main/content/global/skill/prayer/PrayerAltarPlugin.java b/Server/src/main/content/global/skill/prayer/PrayerAltarPlugin.java index 68b7c7add..9c9a7539e 100644 --- a/Server/src/main/content/global/skill/prayer/PrayerAltarPlugin.java +++ b/Server/src/main/content/global/skill/prayer/PrayerAltarPlugin.java @@ -4,7 +4,6 @@ import core.cache.def.impl.SceneryDefinition; import core.game.component.Component; import core.game.node.entity.player.link.prayer.PrayerType; import core.plugin.Initializable; -import core.game.node.entity.skill.SkillRestoration; import core.game.node.entity.skill.Skills; import core.game.interaction.OptionHandler; import core.game.node.Node; diff --git a/Server/src/main/content/global/skill/summoning/familiar/BloatedLeechNPC.java b/Server/src/main/content/global/skill/summoning/familiar/BloatedLeechNPC.java index fc8ba7c12..249b02ce9 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/BloatedLeechNPC.java +++ b/Server/src/main/content/global/skill/summoning/familiar/BloatedLeechNPC.java @@ -5,9 +5,10 @@ import core.game.node.entity.skill.Skills; import core.game.node.entity.combat.ImpactHandler.HitsplatType; import core.game.node.entity.combat.equipment.WeaponInterface; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.tools.RandomFunction; +import static core.api.ContentAPIKt.*; + /** * Represents the Bloated Leech familiar. * @author Aero @@ -38,7 +39,7 @@ public class BloatedLeechNPC extends Familiar { @Override protected boolean specialMove(FamiliarSpecial special) { - owner.getStateManager().remove(EntityState.POISONED); + curePoison(owner); for (int i = 0; i < Skills.SKILL_NAME.length; i++) { if (owner.getSkills().getLevel(i) < owner.getSkills().getStaticLevel(i)) { owner.getSkills().setLevel(i, owner.getSkills().getStaticLevel(i)); diff --git a/Server/src/main/content/global/skill/summoning/familiar/MinotaurFamiliarNPC.java b/Server/src/main/content/global/skill/summoning/familiar/MinotaurFamiliarNPC.java index f5c4d0a07..2fbdf0d35 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/MinotaurFamiliarNPC.java +++ b/Server/src/main/content/global/skill/summoning/familiar/MinotaurFamiliarNPC.java @@ -5,7 +5,6 @@ import core.game.node.entity.combat.CombatStyle; import core.game.node.entity.combat.equipment.WeaponInterface; import core.game.node.entity.impl.Projectile; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.system.task.Pulse; import core.game.world.GameWorld; import core.game.world.update.flag.context.Animation; diff --git a/Server/src/main/content/global/skill/summoning/familiar/SpiritScorpionNPC.java b/Server/src/main/content/global/skill/summoning/familiar/SpiritScorpionNPC.java index f1f3600c2..85bacafa4 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/SpiritScorpionNPC.java +++ b/Server/src/main/content/global/skill/summoning/familiar/SpiritScorpionNPC.java @@ -8,11 +8,12 @@ import core.game.node.entity.combat.equipment.Weapon; import core.game.node.entity.combat.equipment.WeaponInterface; import core.game.node.entity.impl.Projectile; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.node.item.Item; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; +import static core.api.ContentAPIKt.*; + /** * Represents the Spirit Scorpion familiar. * @author Vexia @@ -49,7 +50,7 @@ public class SpiritScorpionNPC extends Familiar { if (isCharged() && new Item(weapon.getId() + 6).getName().startsWith(weapon.getName())) { final Entity victim = state.getVictim(); setCharged(false); - victim.getStateManager().register(EntityState.POISONED, false, 10, owner); + applyPoison(victim, owner, 1); } } } diff --git a/Server/src/main/content/global/skill/summoning/familiar/StrangerPlantNPC.java b/Server/src/main/content/global/skill/summoning/familiar/StrangerPlantNPC.java index fee6e4f21..04d26f299 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/StrangerPlantNPC.java +++ b/Server/src/main/content/global/skill/summoning/familiar/StrangerPlantNPC.java @@ -4,12 +4,13 @@ import core.plugin.Initializable; import core.game.node.entity.Entity; import core.game.node.entity.impl.Projectile; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.node.item.Item; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.tools.RandomFunction; +import static core.api.ContentAPIKt.*; + /** * Represents the Stranger Plant familiar. * @author Aero @@ -45,7 +46,7 @@ public class StrangerPlantNPC extends Forager { } Entity target = special.getTarget(); if (RandomFunction.random(2) == 1) { - target.getStateManager().register(EntityState.POISONED, false, 40, target); + applyPoison(target, owner, 20); } animate(Animation.create(8211)); Projectile.ranged(this, target, 1508, 50, 40, 1, 45).send(); diff --git a/Server/src/main/content/global/skill/summoning/familiar/UnicornStallionNPC.java b/Server/src/main/content/global/skill/summoning/familiar/UnicornStallionNPC.java index f22c8ed32..3bc361957 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/UnicornStallionNPC.java +++ b/Server/src/main/content/global/skill/summoning/familiar/UnicornStallionNPC.java @@ -7,12 +7,13 @@ import core.game.interaction.OptionHandler; import core.game.node.Node; import core.game.node.entity.combat.equipment.WeaponInterface; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.plugin.Plugin; import core.plugin.ClassScanner; +import static core.api.ContentAPIKt.*; + /** * Represents the Unicorn Stallion familiar. * @author Aero @@ -72,13 +73,13 @@ public class UnicornStallionNPC extends Familiar { player.sendMessage("You don't have enough summoning points left"); return true; } - if (!owner.getStateManager().hasState(EntityState.POISONED)) { + if (!isPoisoned(owner)) { player.sendMessage("You are not poisoned."); return true; } player.getAudioManager().send(4372); familiar.visualize(Animation.create(8267), Graphics.create(1356)); - player.getStateManager().remove(EntityState.POISONED); + curePoison(player); player.getSkills().updateLevel(Skills.SUMMONING, -2, 0); return true; } diff --git a/Server/src/main/content/global/skill/summoning/pet/IncubatorHandler.kt b/Server/src/main/content/global/skill/summoning/pet/IncubatorHandler.kt new file mode 100644 index 000000000..79ef7c12d --- /dev/null +++ b/Server/src/main/content/global/skill/summoning/pet/IncubatorHandler.kt @@ -0,0 +1,76 @@ +package content.global.skill.summoning.pet + +import core.api.* +import core.cache.def.impl.SceneryDefinition +import core.game.node.Node +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import core.game.node.item.GroundItemManager +import core.game.interaction.* +import core.tools.StringUtils + +class IncubatorHandler : InteractionListener { + val eggIds = IncubatorEgg.values().map { it.egg.id }.toIntArray() + val incubators = intArrayOf(28550, 28352, 28359) + + override fun defineListeners() { + on (incubators, IntType.SCENERY, "inspect", handler = ::handleInspectOption) + on (incubators, IntType.SCENERY, "take-egg", handler = ::handleTakeOption) + onUseWith (IntType.SCENERY, eggIds, *incubators, handler = ::handleEggOnIncubator) + } + + fun handleEggOnIncubator (player: Player, used: Node, with: Node) : Boolean { + val egg = IncubatorEgg.forItem (used.asItem()) ?: return false + val activeEgg = IncubatorTimer.getEggFor (player, player.location.regionId) + + if (activeEgg != null) { + sendMessage (player, "You already have an egg in this incubator.") + return true + } + + if (removeItem(player, used.asItem())) + IncubatorTimer.registerEgg (player, player.location.regionId, egg) + return true + } + + fun handleInspectOption (player: Player, node: Node) : Boolean { + val activeEgg = IncubatorTimer.getEggFor (player, player.location.regionId) + + if (activeEgg == null) { + sendMessage (player, "The incubator is currently empty.") + return true + } + + if (activeEgg.finished) { + sendMessage (player, "The egg inside has finished incubating.") + return true + } + + val creatureName = activeEgg.egg.product.name.lowercase() + sendMessage (player, "There is currently ${if (StringUtils.isPlusN(creatureName)) "an" else "a"} $creatureName egg incubating.") + return true + } + + fun handleTakeOption (player: Player, node: Node) : Boolean { + val region = player.location.regionId + val activeEgg = IncubatorTimer.getEggFor (player, region) ?: return false + + if (!activeEgg.finished) { + sendMessage (player, "That egg hasn't finished incubating!") + return true + } + + if (freeSlots(player) < 1) { + sendMessage (player, "You do not have enough inventory space to do that.") + return true + } + + val egg = IncubatorTimer.removeEgg (player, region) ?: return false + val product = egg.product + val name = product.name.lowercase() + + sendMessage(player, "You take your $name out of the incubator.") + addItem(player, product.id) + return true + } +} diff --git a/Server/src/main/content/global/skill/summoning/pet/IncubatorState.kt b/Server/src/main/content/global/skill/summoning/pet/IncubatorState.kt deleted file mode 100644 index dfe5adf8e..000000000 --- a/Server/src/main/content/global/skill/summoning/pet/IncubatorState.kt +++ /dev/null @@ -1,65 +0,0 @@ -package content.global.skill.summoning.pet - -import core.game.node.entity.player.Player -import core.game.node.entity.state.PlayerState -import core.game.node.entity.state.State -import core.game.system.task.Pulse -import org.json.simple.JSONObject -import core.api.* -import core.tools.* - -@PlayerState("incubator") -class IncubatorState(player: Player? = null) : State(player) { - var egg: IncubatorEgg? = null - var completionTimeMs = 0L - - fun setTicksLeft (ticks: Int) { - completionTimeMs = System.currentTimeMillis() + (ticksToSeconds(ticks) * 1000) - } - - override fun newInstance(player: Player?): State { - return IncubatorState(player) - } - - override fun save(root: JSONObject) { - if(pulse == null) return - - val data = JSONObject() - data.put("eggOrdinal",(egg?.ordinal ?: 0).toString()) - data.put("endTime", completionTimeMs.toString()) - root.put("eggdata",data) - } - - override fun parse(_data: JSONObject) { - if(_data.containsKey("eggdata")){ - val data = _data["eggdata"] as JSONObject - egg = IncubatorEgg.values()[data["eggOrdinal"].toString().toInt()] - - if (data.containsKey("ticksLeft")) - completionTimeMs = System.currentTimeMillis() + ticksToSeconds(data["ticksLeft"].toString().toInt() * 1000) - else - completionTimeMs = data["endTime"].toString().toLong() - } - } - - override fun createPulse() { - player ?: return - egg ?: return - setVarbit(player, 4277, 1) - pulse = object : Pulse(){ - override fun pulse(): Boolean { - if(System.currentTimeMillis() >= completionTimeMs){ - player.setAttribute(ATTR_INCUBATOR_PRODUCT, egg!!.ordinal) - player.sendMessage("Your " + egg!!.product.name.toLowerCase() + " has finished hatching.") - pulse = null - return true - } - return false - } - } - } - - companion object { - const val ATTR_INCUBATOR_PRODUCT = "/save:incubator:egg-product" - } -} diff --git a/Server/src/main/content/global/skill/summoning/pet/IncubatorTimer.kt b/Server/src/main/content/global/skill/summoning/pet/IncubatorTimer.kt new file mode 100644 index 000000000..6395a79eb --- /dev/null +++ b/Server/src/main/content/global/skill/summoning/pet/IncubatorTimer.kt @@ -0,0 +1,98 @@ +package content.global.skill.summoning.pet + +import core.api.* +import core.tools.* +import core.game.system.timer.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import java.util.* +import org.json.simple.* + +class IncubatorTimer : PersistTimer (500, "incubation") { + val incubatingEggs = HashMap() + + override fun getInitialRunDelay() : Int { + return 50 + } + + override fun parse (root: JSONObject, entity: Entity) { + val eggs = root["eggs"] as? JSONArray ?: return + for (eggData in eggs) { + val eggInfo = eggData as JSONArray + val egg = IncubatingEgg ( + eggInfo[0].toString().toInt(), + IncubatorEgg.values()[eggInfo[1].toString().toInt()], + eggInfo[2].toString().toLong(), + eggInfo[3].toString().toBoolean() + ) + incubatingEggs[egg.region] = egg + } + } + + override fun save (root: JSONObject, entity: Entity) { + val arr = JSONArray() + for ((_, eggInfo) in incubatingEggs) { + val eggArr = JSONArray() + eggArr.add(eggInfo.region.toString()) + eggArr.add(eggInfo.egg.ordinal.toString()) + eggArr.add(eggInfo.endTime.toString()) + eggArr.add(eggInfo.finished) + arr.add(eggArr) + } + root["eggs"] = arr + } + + override fun run (entity: Entity) : Boolean { + if (entity !is Player) return false + for ((_, egg) in incubatingEggs) { + if (egg.finished) continue + if (egg.isDone()) { + sendMessage(entity, colorize("%RYour ${egg.egg.product.name.lowercase()} egg has finished hatching.")) + egg.finished = true + } + } + return !incubatingEggs.isEmpty() + } + + data class IncubatingEgg (val region: Int, val egg: IncubatorEgg, var endTime: Long, var finished: Boolean = false) + fun IncubatingEgg.isDone() : Boolean { + return endTime < System.currentTimeMillis() + } + + companion object { + val TAVERLY_REGION = 11573 + val TAVERLY_VARBIT = 4277 + val YANILLE_REGION = 10288 + val YANILLE_VARBIT = 4221 + + fun varbitForRegion (region: Int) : Int { + return when (region) { + TAVERLY_REGION -> TAVERLY_VARBIT + YANILLE_REGION -> YANILLE_VARBIT + else -> -1 + } + } + + fun getEggFor (player: Player, region: Int) : IncubatingEgg? { + val playerTimer = getTimer(player) ?: return null + return playerTimer.incubatingEggs[region] + } + + fun registerEgg (player: Player, region: Int, egg: IncubatorEgg) { + val timer = getTimer(player) ?: IncubatorTimer() + timer.incubatingEggs [region] = IncubatingEgg (region, egg, System.currentTimeMillis() + (ticksToSeconds(egg.inucbationTime * 100) * 1000)) + + if (!hasTimerActive(player)) + registerTimer(player, timer) + setVarbit(player, varbitForRegion(region), 1, true) + } + + fun removeEgg (player: Player, region: Int) : IncubatorEgg? { + val egg = getEggFor (player, region) ?: return null + val timer = getTimer(player) ?: return null + timer.incubatingEggs.remove(region) + setVarbit(player, varbitForRegion(region), 0, true) + return egg.egg + } + } +} diff --git a/Server/src/main/content/global/skill/thieving/ThievingListeners.kt b/Server/src/main/content/global/skill/thieving/ThievingListeners.kt index 4f9050e2e..c1f881a32 100644 --- a/Server/src/main/content/global/skill/thieving/ThievingListeners.kt +++ b/Server/src/main/content/global/skill/thieving/ThievingListeners.kt @@ -7,7 +7,6 @@ import core.game.node.entity.impl.Animator import core.game.node.entity.player.Player import core.game.node.entity.player.link.audio.Audio import core.game.node.entity.skill.Skills -import core.game.node.entity.state.EntityState import core.game.world.update.flag.context.Animation import core.tools.RandomFunction import org.rs09.consts.Items @@ -75,4 +74,4 @@ class ThievingListeners : InteractionListener { return@on true } } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/state/DiseasedState.kt b/Server/src/main/content/global/state/DiseasedState.kt deleted file mode 100644 index 9dbadb0cf..000000000 --- a/Server/src/main/content/global/state/DiseasedState.kt +++ /dev/null @@ -1,53 +0,0 @@ -package content.global.state - -import core.game.node.entity.combat.ImpactHandler -import core.game.node.entity.player.Player -import core.game.system.task.Pulse -import core.tools.RandomFunction -import org.json.simple.JSONObject -import core.game.node.entity.state.PlayerState -import core.game.node.entity.state.State -import core.game.world.GameWorld - -@PlayerState("disease") -class DiseasedState(player: Player? = null) : State(player){ - var hitsLeft = 25 - - override fun save(root: JSONObject) { - if(hitsLeft > 0){ - root.put("hitsLeft",hitsLeft) - } - } - - override fun parse(_data: JSONObject) { - if(_data.containsKey("hitsLeft")){ - hitsLeft = _data["hitsLeft"].toString().toInt() - } - } - - override fun newInstance(player: Player?): State { - return DiseasedState(player) - } - - override fun createPulse() { - player ?: return - if(player.getAttribute("immunity:disease",0) > GameWorld.ticks){ - return - } - if(hitsLeft <= 0) return - player.sendMessage("You have been diseased!") - pulse = object : Pulse(30){ - override fun pulse(): Boolean { - val damage = RandomFunction.random(1,5) - player.impactHandler.manualHit(player,damage,ImpactHandler.HitsplatType.DISEASE) - var skillId = RandomFunction.random(24) - if(skillId == 3) skillId-- - player.skills.updateLevel(skillId,-damage,0) - hitsLeft-- - if(hitsLeft <= 0) player.sendMessage("The disease has wore off.") - return hitsLeft <= 0 - } - } - } - -} \ No newline at end of file diff --git a/Server/src/main/content/minigame/bountyhunter/BountyLocateSpell.java b/Server/src/main/content/minigame/bountyhunter/BountyLocateSpell.java index 672c2d8bf..36693d2f0 100644 --- a/Server/src/main/content/minigame/bountyhunter/BountyLocateSpell.java +++ b/Server/src/main/content/minigame/bountyhunter/BountyLocateSpell.java @@ -10,14 +10,13 @@ import core.game.node.entity.combat.spell.SpellType; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.SpellBookManager.SpellBook; import core.game.node.entity.player.link.TeleportManager.TeleportType; -import core.game.node.entity.state.EntityState; import core.game.node.item.Item; import core.game.world.GameWorld; import core.game.world.map.Location; import core.game.world.map.RegionManager; import core.plugin.Plugin; -import static core.api.ContentAPIKt.isStunned; +import static core.api.ContentAPIKt.*; /** * Handles the bounty target locate spell. @@ -45,7 +44,7 @@ public final class BountyLocateSpell extends MagicSpell { player.getPacketDispatch().sendMessage("You don't have a target to teleport to."); return true; } - if (player.getStateManager().hasState(EntityState.FROZEN) || isStunned(player)) { + if (hasTimerActive(player, "frozen") || isStunned(player)) { player.getPacketDispatch().sendMessage("You can't use this when " + (isStunned(player) ? "stunned." : "frozen.")); return true; } @@ -98,4 +97,4 @@ public final class BountyLocateSpell extends MagicSpell { return this; } -} \ No newline at end of file +} diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt index 1932de737..ab1089bdc 100644 --- a/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsArea.kt @@ -1,15 +1,10 @@ package rs09.game.content.activity.castlewars.areas import content.global.skill.summoning.familiar.BurdenBeast -import core.api.LogoutListener -import core.api.MapArea -import core.api.log -import core.api.sendMessage +import core.api.* import core.game.interaction.InteractionListener import core.game.node.entity.Entity import core.game.node.entity.player.Player -import core.game.node.entity.state.EntityState -import core.tools.Log import rs09.game.content.activity.castlewars.CastleWars abstract class CastleWarsArea : MapArea, LogoutListener, InteractionListener { @@ -51,7 +46,7 @@ abstract class CastleWarsArea : MapArea, LogoutListener, InteractionListener { player.interfaceManager.closeOverlay() // Remove teleblock - player.stateManager.remove(EntityState.TELEBLOCK) + removeTimer(player, "teleblock") // Remove any Castle Wars items // Todo Remove any tinderboxes or other castle wars items - See Jan 2018 update: https://oldschool.runescape.wiki/w/Castle_Wars @@ -78,4 +73,4 @@ abstract class CastleWarsArea : MapArea, LogoutListener, InteractionListener { } } -} \ No newline at end of file +} diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt index 0999555d9..2b32486aa 100644 --- a/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsGameArea.kt @@ -1,11 +1,9 @@ package rs09.game.content.activity.castlewars.areas -import core.api.TickListener -import core.api.log +import core.api.* import core.game.component.Component import core.game.node.entity.Entity import core.game.node.entity.player.Player -import core.game.node.entity.state.EntityState import core.game.node.item.Item import core.game.world.map.Location import core.game.world.map.zone.ZoneBorders @@ -72,8 +70,7 @@ class CastleWarsGameArea : CastleWarsArea(), TickListener { override fun areaEnter(entity: Entity) { val player = entity as? Player ?: return super.areaEnter(player) - // Block player teleport (for the remaining duration of the game) - player.stateManager.set(EntityState.TELEBLOCK, (CastleWars.gameTimeMinutes)*60*2) + registerTimer (player, spawnTimer("teleblock", (CastleWars.gameTimeMinutes)*60*2)) if (saradominPlayers.contains(player)) { player.interfaceManager.openOverlay(Component(Components.CASTLEWARS_STATUS_OVERLAY_SARADOMIN_58)) diff --git a/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt b/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt index cd7f3334a..3bc53ddd2 100644 --- a/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt +++ b/Server/src/main/content/minigame/castlewars/areas/CastleWarsWaitingArea.kt @@ -1,14 +1,10 @@ package rs09.game.content.activity.castlewars.areas import CastleWarsOverlay -import core.api.God -import core.api.TickListener -import core.api.hasGodItem -import core.api.sendDialogue +import core.api.* import core.game.component.Component import core.game.node.entity.Entity import core.game.node.entity.player.Player -import core.game.node.entity.state.EntityState import core.game.node.item.Item import core.game.world.map.zone.ZoneBorders import core.tools.ticksPerMinute @@ -37,8 +33,7 @@ class CastleWarsWaitingArea : CastleWarsArea(), TickListener { override fun areaEnter(entity: Entity) { val player = entity as? Player ?: return super.areaEnter(player) - // Block player teleport (for at least the max wait time) - player.stateManager.set(EntityState.TELEBLOCK, (CastleWars.gameCooldownMinutes + CastleWars.gameTimeMinutes)*60*2) + registerTimer(player, spawnTimer("teleblock", (CastleWars.gameCooldownMinutes + CastleWars.gameTimeMinutes)*60*2)) // Set team attribute and equip the hooded cloak on the entity based on which waiting room they're in if (zamorakWaitingRoom.insideBorder(player.location)) { diff --git a/Server/src/main/content/minigame/duel/DuelSession.java b/Server/src/main/content/minigame/duel/DuelSession.java index 018b7dcdf..e28c9bfab 100644 --- a/Server/src/main/content/minigame/duel/DuelSession.java +++ b/Server/src/main/content/minigame/duel/DuelSession.java @@ -12,7 +12,6 @@ import core.game.container.impl.EquipmentContainer; import core.game.node.entity.player.Player; import core.game.node.entity.player.info.LogType; import core.game.node.entity.player.info.login.PlayerParser; -import core.game.node.entity.state.EntityState; import core.game.node.item.Item; import core.plugin.Plugin; import core.tools.RandomFunction; @@ -205,8 +204,8 @@ public final class DuelSession extends ComponentPlugin { */ public void heal(Player p) { p.fullRestore(); - if (p.getStateManager().hasState(EntityState.POISONED)) { - p.getStateManager().remove(EntityState.POISONED); + if (isPoisoned(p)) { + curePoison(p); } p.getSkills().restore(); } diff --git a/Server/src/main/content/minigame/fishingtrawler/FishingTrawlerSession.kt b/Server/src/main/content/minigame/fishingtrawler/FishingTrawlerSession.kt index be8c1cd9f..097480e4e 100644 --- a/Server/src/main/content/minigame/fishingtrawler/FishingTrawlerSession.kt +++ b/Server/src/main/content/minigame/fishingtrawler/FishingTrawlerSession.kt @@ -3,14 +3,13 @@ package content.minigame.fishingtrawler import core.api.LogoutListener import core.api.MapArea import core.api.getRegionBorders -import core.api.log +import core.api.* import core.game.component.Component import core.game.node.entity.Entity import core.game.node.scenery.Scenery import core.game.node.scenery.SceneryBuilder import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player -import core.game.node.entity.state.EntityState import core.game.node.item.Item import core.game.system.task.Pulse import core.game.world.GameWorld @@ -81,7 +80,7 @@ class FishingTrawlerSession(val activity: FishingTrawlerActivity? = null) : Logo updateOverlay(player) player.properties.teleportLocation = base.transform(36,24,0) player.setAttribute("ft-session",this) - player.stateManager.set(EntityState.TELEBLOCK,timeLeft) + registerTimer (player, spawnTimer("teleblock", timeLeft)) } zone.register(getRegionBorders(region.id)) } @@ -105,7 +104,7 @@ class FishingTrawlerSession(val activity: FishingTrawlerActivity? = null) : Logo player.appearance.setAnimations(Animation(188)) player.properties.teleportLocation = session.base.transform(36,24,0) player.incrementAttribute("/save:$STATS_BASE:$FISHING_TRAWLER_SHIPS_SANK") - player.stateManager.remove(EntityState.TELEBLOCK) + removeTimer(player, "teleblock") } return true } diff --git a/Server/src/main/content/minigame/pestcontrol/PestControlActivityPlugin.java b/Server/src/main/content/minigame/pestcontrol/PestControlActivityPlugin.java index 4e2235981..f1fb680a0 100644 --- a/Server/src/main/content/minigame/pestcontrol/PestControlActivityPlugin.java +++ b/Server/src/main/content/minigame/pestcontrol/PestControlActivityPlugin.java @@ -15,7 +15,6 @@ import core.game.node.entity.Entity; import core.game.node.entity.impl.PulseManager; import core.game.node.entity.player.Player; import core.game.node.entity.player.info.Rights; -import core.game.node.entity.state.EntityState; import core.game.node.item.GroundItemManager; import core.game.node.item.Item; import core.game.system.task.Pulse; @@ -30,6 +29,8 @@ import core.plugin.ClassScanner; import core.tools.RandomFunction; import core.tools.StringUtils; +import static core.api.ContentAPIKt.*; + /** * Handles the Pest Control activity. @@ -147,8 +148,8 @@ public final class PestControlActivityPlugin extends ActivityPlugin { p.removeAttribute("pc_zeal"); p.removeExtension(PestControlSession.class); p.fullRestore(); - if (p.getStateManager().hasState(EntityState.POISONED)) { - p.getStateManager().remove(EntityState.POISONED); + if (isPoisoned(p)) { + curePoison(p); } PulseManager.cancelDeathTask(p); GameWorld.getPulser().submit(new Pulse(1, p) { diff --git a/Server/src/main/content/minigame/pestcontrol/monsters/PCSpinnerNPC.java b/Server/src/main/content/minigame/pestcontrol/monsters/PCSpinnerNPC.java index a2da842b6..60cc22a37 100644 --- a/Server/src/main/content/minigame/pestcontrol/monsters/PCSpinnerNPC.java +++ b/Server/src/main/content/minigame/pestcontrol/monsters/PCSpinnerNPC.java @@ -11,7 +11,6 @@ import core.game.node.entity.impl.PulseType; import core.game.node.entity.npc.AbstractNPC; import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.system.task.Pulse; import core.game.world.GameWorld; import core.game.world.map.Location; @@ -20,6 +19,8 @@ import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.tools.RandomFunction; +import static core.api.ContentAPIKt.*; + /** * Handles a pest control spinner NPC. * @author Emperor @@ -101,8 +102,7 @@ public final class PCSpinnerNPC extends AbstractNPC { animate(getProperties().getDeathAnimation()); for (Player p : RegionManager.getLocalPlayers(this, 1)) { p.getImpactHandler().manualHit(this, 5, HitsplatType.POISON); - p.setAttribute("/save:poison_damage", 18); - p.getStateManager().register(EntityState.POISONED, false, 18, this); + applyPoison(p, this, 1); } GameWorld.getPulser().submit(new Pulse(1, this) { @Override diff --git a/Server/src/main/content/minigame/pyramidplunder/PyramidPlunderMinigame.kt b/Server/src/main/content/minigame/pyramidplunder/PyramidPlunderMinigame.kt index 7f8d72b37..8798259a1 100644 --- a/Server/src/main/content/minigame/pyramidplunder/PyramidPlunderMinigame.kt +++ b/Server/src/main/content/minigame/pyramidplunder/PyramidPlunderMinigame.kt @@ -3,7 +3,6 @@ package content.minigame.pyramidplunder import core.api.* import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills -import core.game.node.entity.state.EntityState import core.game.system.task.Pulse import core.game.world.map.Direction import core.game.world.map.Location @@ -117,7 +116,7 @@ class PyramidPlunderMinigame : InteractionListener, TickListener, LogoutListener animate(player, URN_BIT) sendMessage(player, "You've been bitten by something moving around in the urn.") impact(player, RandomFunction.random(1,5)) - player.stateManager.register(EntityState.POISONED, true, 20, player) + applyPoison(player, player, 2) } else { animate(player, URN_SUCCESS) @@ -169,7 +168,7 @@ class PyramidPlunderMinigame : InteractionListener, TickListener, LogoutListener animate(player, URN_BIT) sendMessage(player, "You've been bitten by something moving around in the urn.") impact(player, RandomFunction.random(1,5)) - player.stateManager.register(EntityState.POISONED, true, 20, player) + applyPoison(player, player, 2) } else { animate(player, URN_SUCCESS) diff --git a/Server/src/main/content/minigame/vinesweeper/Vinesweeper.kt b/Server/src/main/content/minigame/vinesweeper/Vinesweeper.kt index 1763d9f4e..0c3cb10ce 100644 --- a/Server/src/main/content/minigame/vinesweeper/Vinesweeper.kt +++ b/Server/src/main/content/minigame/vinesweeper/Vinesweeper.kt @@ -13,7 +13,6 @@ import core.game.node.entity.npc.AbstractNPC import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills -import core.game.node.entity.state.EntityState import core.game.node.item.GroundItemManager import core.game.node.item.Item import core.game.node.scenery.Scenery @@ -498,7 +497,7 @@ class Vinesweeper : InteractionListener, InterfaceListener, MapArea { object VinesweeperTeleport { @JvmStatic fun teleport(npc: NPC, player: Player) { - if (player.stateManager.hasState(EntityState.TELEBLOCK)) { + if (hasTimerActive(player, "teleblock")) { sendNPCDialogue(player, npc.id, "I can't do that, you're teleblocked!", core.game.dialogue.FacialExpression.OLD_ANGRY1) return } diff --git a/Server/src/main/content/region/asgarnia/trollheim/handlers/gwd/GWDTsutsarothSwingHandler.java b/Server/src/main/content/region/asgarnia/trollheim/handlers/gwd/GWDTsutsarothSwingHandler.java index b8c40462e..d90bd3f35 100644 --- a/Server/src/main/content/region/asgarnia/trollheim/handlers/gwd/GWDTsutsarothSwingHandler.java +++ b/Server/src/main/content/region/asgarnia/trollheim/handlers/gwd/GWDTsutsarothSwingHandler.java @@ -9,11 +9,12 @@ import core.game.node.entity.combat.equipment.ArmourSet; import core.game.node.entity.impl.Projectile; import core.game.node.entity.impl.Animator.Priority; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.tools.RandomFunction; +import static core.api.ContentAPIKt.*; + /** * Handles K'ril Tsutsaroth's combat. * @author Emperor @@ -72,7 +73,7 @@ public final class GWDTsutsarothSwingHandler extends CombatSwingHandler { hit = RandomFunction.random(max); state.setMaximumHit(max); if (style == CombatStyle.MELEE) { - victim.getStateManager().register(EntityState.POISONED, false, 168, entity); + applyPoison(victim, entity, 16); } if (special) { ((Player) victim).getSkills().decrementPrayerPoints((double) hit / 2); @@ -152,4 +153,4 @@ public final class GWDTsutsarothSwingHandler extends CombatSwingHandler { return getType().getSwingHandler().calculateHit(entity, victim, modifier); } -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/fremennik/waterbirth/handlers/SpinolypNPC.java b/Server/src/main/content/region/fremennik/waterbirth/handlers/SpinolypNPC.java index 9fe83d2e9..7836a1c2c 100644 --- a/Server/src/main/content/region/fremennik/waterbirth/handlers/SpinolypNPC.java +++ b/Server/src/main/content/region/fremennik/waterbirth/handlers/SpinolypNPC.java @@ -8,12 +8,13 @@ import core.game.node.entity.combat.InteractionType; import core.game.node.entity.combat.equipment.SwitchAttack; import core.game.node.entity.npc.AbstractNPC; import core.game.node.entity.player.link.SpellBookManager.SpellBook; -import core.game.node.entity.state.EntityState; import core.game.world.map.Location; import core.tools.RandomFunction; import core.game.node.entity.combat.CombatSwingHandler; import core.game.node.entity.combat.MultiSwingHandler; +import static core.api.ContentAPIKt.*; + /** * Represents a spinolyp npc. * @author Vexia @@ -147,7 +148,7 @@ public final class SpinolypNPC extends AbstractNPC { victim.getSkills().decrementPrayerPoints(1); } else { if (RandomFunction.random(20) == 5) { - victim.getStateManager().register(EntityState.POISONED, false, 68, entity); + applyPoison(victim, entity, 30); } } } diff --git a/Server/src/main/content/region/kandarin/quest/dwarfcannon/dmc/CannonTimer.kt b/Server/src/main/content/region/kandarin/quest/dwarfcannon/dmc/CannonTimer.kt new file mode 100644 index 000000000..064c341af --- /dev/null +++ b/Server/src/main/content/region/kandarin/quest/dwarfcannon/dmc/CannonTimer.kt @@ -0,0 +1,34 @@ +package content.region.kandarin.quest.dwarfcannon.dmc + +import core.api.* +import core.tools.* +import core.game.system.timer.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player + +class CannonTimer : RSTimer (1, "dmc:timer") { + lateinit var dmcHandler: DMCHandler + var ticksUntilDecay = 2500 + var isFiring = false + + override fun run (entity: Entity) : Boolean { + if (entity !is Player) + return false + if (!dmcHandler.cannon.isActive()) + return false + if (isFiring) + isFiring = dmcHandler.rotate() + if (--ticksUntilDecay == 500) { + sendMessage (entity, colorize("%RYour cannon is about to decay.")) + } else if (ticksUntilDecay == 0) { + dmcHandler.explode(true) + } + return ticksUntilDecay > 0 && dmcHandler.cannon.isActive() + } + + override fun getTimer (vararg args: Any) : RSTimer { + val t = retrieveInstance() as CannonTimer + t.dmcHandler = args[0] as DMCHandler + return t + } +} diff --git a/Server/src/main/content/region/kandarin/quest/dwarfcannon/dmc/DMCHandler.java b/Server/src/main/content/region/kandarin/quest/dwarfcannon/dmc/DMCHandler.java index 5dd8a9439..292f30a9d 100644 --- a/Server/src/main/content/region/kandarin/quest/dwarfcannon/dmc/DMCHandler.java +++ b/Server/src/main/content/region/kandarin/quest/dwarfcannon/dmc/DMCHandler.java @@ -22,6 +22,8 @@ import org.jetbrains.annotations.NotNull; import core.game.node.entity.combat.CombatSwingHandler; import core.game.world.GameWorld; +import static core.api.ContentAPIKt.*; + /** * Handles a player's Dwarf Multi-cannon. * @author Emperor @@ -43,23 +45,15 @@ public final class DMCHandler implements LogoutListener { */ private int cannonballs; - /** - * The firing pulse. - */ - private Pulse firingPulse; - /** * The current direction. */ private DMCRevolution direction = DMCRevolution.NORTH; - /** - * The decaying pulse. - */ - private Pulse decayPulse; + private CannonTimer timer; public DMCHandler() { - this.player = null; + this.player = null; } /** @@ -68,44 +62,16 @@ public final class DMCHandler implements LogoutListener { */ public DMCHandler(final Player player) { this.player = player; - this.firingPulse = new Pulse(1, player) { - @Override - public boolean pulse() { - if (!cannon.isActive()) { - return true; - } - return rotate(); - } - - }; - firingPulse.stop(); - this.decayPulse = new Pulse(2000, player) { - @Override - public boolean pulse() { - if (!cannon.isActive()) { - return true; - } - if (getDelay() == 2000) { - setDelay(500); - player.sendMessage("Your cannon is about to decay!"); - return false; - } - explode(true); - return true; - } - - }; - decayPulse.stop(); } /** * Rotates the cannon. * @return {@code True} if the cannon should stop rotating. */ - private boolean rotate() { + public boolean rotate() { if (cannonballs < 1) { player.getPacketDispatch().sendMessage("Your cannon has run out of ammo!"); - return true; + return false; } player.getPacketDispatch().sendSceneryAnimation(cannon, Animation.create(direction.getAnimationId())); Location l = cannon.getLocation().transform(1, 1, 0); @@ -127,7 +93,7 @@ public final class DMCHandler implements LogoutListener { break; } } - return false; + return true; } /** @@ -138,9 +104,9 @@ public final class DMCHandler implements LogoutListener { player.getPacketDispatch().sendMessage("You don't have a cannon active."); return; } - if (firingPulse.isRunning()) { - firingPulse.stop(); - return; + if (timer.isFiring()) { + timer.setFiring(false); + return; } if (cannonballs < 1) { int amount = player.getInventory().getAmount(new Item(2)); @@ -160,9 +126,7 @@ public final class DMCHandler implements LogoutListener { player.sendMessage("Your cannon is already fully loaded."); } } - firingPulse.restart(); - firingPulse.start(); - GameWorld.getPulser().submit(firingPulse); + timer.setFiring(true); } /** @@ -193,10 +157,6 @@ public final class DMCHandler implements LogoutListener { return; } final DMCHandler handler = new DMCHandler(player); - if (handler.decayPulse.isRunning()) { - handler.decayPulse.stop(); - return; - } player.setAttribute("dmc", handler); player.getPulseManager().clear(); player.getWalkingQueue().reset(); @@ -233,6 +193,8 @@ public final class DMCHandler implements LogoutListener { player.getPacketDispatch().sendMessage("You add the furnace."); SceneryBuilder.remove(object); handler.configure(SceneryBuilder.add(object = object.transform(6))); + handler.timer = (CannonTimer) spawnTimer ("dmc:timer", handler); + registerTimer (player, handler.timer); return true; } player.getAudioManager().send(new Audio(2876), true); @@ -267,9 +229,6 @@ public final class DMCHandler implements LogoutListener { * @param pickup If the cannon is getting picked up. */ public void clear(boolean pickup) { - if (decayPulse.isRunning()) { - decayPulse.stop(); - } SceneryBuilder.remove(cannon); player.removeAttribute("dmc"); if (!pickup) { diff --git a/Server/src/main/content/region/kandarin/yanille/handlers/YanilleAgilityDungeon.kt b/Server/src/main/content/region/kandarin/yanille/handlers/YanilleAgilityDungeon.kt index 1c26cee2c..7e954345f 100644 --- a/Server/src/main/content/region/kandarin/yanille/handlers/YanilleAgilityDungeon.kt +++ b/Server/src/main/content/region/kandarin/yanille/handlers/YanilleAgilityDungeon.kt @@ -10,7 +10,6 @@ import core.game.node.entity.combat.BattleState import core.game.node.entity.combat.CombatStyle import core.game.node.entity.npc.AbstractNPC import core.game.node.entity.player.Player -import core.game.node.entity.state.EntityState import core.game.node.item.Item import core.game.node.scenery.Scenery import core.game.node.scenery.SceneryBuilder @@ -72,7 +71,7 @@ public class YanilleAgilityDungeonListeners : InteractionListener { player.lock(1) if(player.inventory.remove(Item(Items.SINISTER_KEY_993, 1))) { player.sendMessages("You unlock the chest with your key...", "A foul gas seeps from the chest"); - player.getStateManager().register(EntityState.POISONED, true, 28, player); + applyPoison (player, player, 2) for(item in SINISTER_CHEST_HERBS) { addItemOrDrop(player, item.id, item.amount) } diff --git a/Server/src/main/content/region/wilderness/handlers/DarkEnergyCoreNPC.java b/Server/src/main/content/region/wilderness/handlers/DarkEnergyCoreNPC.java index 2e8c5fbf8..bcc8930cd 100644 --- a/Server/src/main/content/region/wilderness/handlers/DarkEnergyCoreNPC.java +++ b/Server/src/main/content/region/wilderness/handlers/DarkEnergyCoreNPC.java @@ -6,14 +6,13 @@ import core.game.node.entity.impl.Projectile; import core.game.node.entity.npc.AbstractNPC; import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.system.task.Pulse; import core.game.world.GameWorld; import core.game.world.map.Location; import core.plugin.Initializable; import core.tools.RandomFunction; -import static core.api.ContentAPIKt.isStunned; +import static core.api.ContentAPIKt.*; /** * Handles the Dark Energy Core NPC. @@ -71,7 +70,7 @@ public final class DarkEnergyCoreNPC extends AbstractNPC { @Override public void handleTickActions() { ticks++; - boolean poisoned = getStateManager().hasState(EntityState.POISONED); + boolean poisoned = isPoisoned(this); if (isStunned(this) || isInvisible()) { return; } diff --git a/Server/src/main/content/region/wilderness/handlers/SkulledState.kt b/Server/src/main/content/region/wilderness/handlers/SkulledState.kt deleted file mode 100644 index a23fba064..000000000 --- a/Server/src/main/content/region/wilderness/handlers/SkulledState.kt +++ /dev/null @@ -1,45 +0,0 @@ -package content.region.wilderness.handlers - -import core.game.node.entity.player.Player -import core.game.system.task.Pulse -import org.json.simple.JSONObject -import core.game.node.entity.state.PlayerState -import core.game.node.entity.state.State - -@PlayerState("skull") -class SkulledState(player: Player? = null) : State(player) { - var ticksLeft = 2000 - - override fun save(root: JSONObject) { - root.put("ticksLeft",ticksLeft) - } - - override fun parse(_data: JSONObject) { - if(_data.containsKey("ticksLeft")){ - ticksLeft = _data["ticksLeft"].toString().toInt() - } - } - - override fun newInstance(player: Player?): State { - return SkulledState(player) - } - - override fun createPulse() { - player ?: return - if(ticksLeft <= 0) return - player.skullManager.setSkullIcon(0) - player.skullManager.isSkulled = true - pulse = object : Pulse(){ - override fun pulse(): Boolean { - ticksLeft-- - if(ticksLeft <= 0) { - player.skullManager.reset() - pulse = null - return true - } - return false - } - } - } - -} \ No newline at end of file diff --git a/Server/src/main/content/region/wilderness/handlers/revenants/RevenantCombatHandler.java b/Server/src/main/content/region/wilderness/handlers/revenants/RevenantCombatHandler.java index 3beb7a61f..bf8d686b2 100644 --- a/Server/src/main/content/region/wilderness/handlers/revenants/RevenantCombatHandler.java +++ b/Server/src/main/content/region/wilderness/handlers/revenants/RevenantCombatHandler.java @@ -7,13 +7,14 @@ import core.game.node.entity.combat.equipment.SwitchAttack; import core.game.node.entity.impl.Projectile; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.prayer.PrayerType; -import core.game.node.entity.state.EntityState; import core.game.world.map.zone.impl.WildernessZone; import core.game.world.update.flag.context.Animation; import core.game.world.update.flag.context.Graphics; import core.game.node.entity.combat.MultiSwingHandler; import core.game.world.GameWorld; +import static core.api.ContentAPIKt.*; + /** * Handles the multi swing combat handler for revenants. * @author Vexia @@ -55,24 +56,25 @@ public class RevenantCombatHandler extends MultiSwingHandler { if (victim instanceof Player) { SwitchAttack attack = getCurrent(); if (attack != null) { - if (attack.getStyle() == CombatStyle.RANGE && victim.getAttribute("freeze_immunity", -1) < GameWorld.getTicks()) { - victim.getStateManager().set(EntityState.FROZEN, 16, "The icy darts freeze your muscles!"); + if (attack.getStyle() == CombatStyle.RANGE && !hasTimerActive(victim, "frozen") && !hasTimerActive(victim, "frozen:immunity")) { + registerTimer(victim, spawnTimer("frozen", 16, true)); + sendMessage((Player) victim, "The icy darts freeze your muscles!"); victim.asPlayer().getAudioManager().send(4059, true); } else if (attack.getStyle() == CombatStyle.MAGIC) { int ticks = 500; if (victim.asPlayer().getPrayer().get(PrayerType.PROTECT_FROM_MAGIC)) { ticks /= 2; } - if (victim.getStateManager().hasState(EntityState.TELEBLOCK)) { + if (hasTimerActive(victim, "teleblock")) { victim.asPlayer().getAudioManager().send(4064, true); } else { - victim.getStateManager().set(EntityState.TELEBLOCK, ticks); + registerTimer (victim, spawnTimer("teleblock", ticks)); } } } } - if (!victim.getStateManager().hasState(EntityState.POISONED) && (WildernessZone.getWilderness(entity) >= 50 || entity.getId() == 6998)) { - victim.getStateManager().register(EntityState.POISONED, false, 68, entity); + if (!isPoisoned(victim) && (WildernessZone.getWilderness(entity) >= 50 || entity.getId() == 6998)) { + applyPoison(victim, entity, 6); } super.impact(entity, victim, state); } diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index acd2eae3f..c27e33bb7 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -25,7 +25,6 @@ import core.game.node.entity.skill.Skills import content.data.skill.SkillingTool import content.global.skill.slayer.Tasks import content.global.skill.summoning.familiar.BurdenBeast -import core.game.node.entity.state.EntityState import core.game.node.item.GroundItem import core.game.node.item.GroundItemManager import core.game.node.item.Item @@ -74,6 +73,8 @@ import core.game.requirement.* import core.net.packet.PacketRepository import core.net.packet.context.MusicContext import core.net.packet.out.MusicPacket +import core.game.system.timer.* +import core.game.system.timer.impl.* import java.util.regex.* import java.io.* import kotlin.math.* @@ -963,6 +964,131 @@ fun removeAttributes(entity: Entity, vararg attributes: String) { for (attribute in attributes) removeAttribute(entity, attribute) } +/** + * Adds the given timer to the entity's active timers. + * @param entity the entity whose timers are being added to + * @param timer the timer being added +**/ +fun registerTimer (entity: Entity, timer: RSTimer?) { + if (timer == null) return + entity.timers.registerTimer (timer) +} + +/** + * Used to fetch the existing, active, non-abstract and non-anonymous timer with the given identifier, or start a new timer if none exists and return that. + * @param entity the entity whose timers we're retrieving + * @param identifier the identifier of the timer, refer to the individual timer class for this token. + * @param args various args to pass to the initialization of the timer, if applicable. + * @return Either the existing active timer, or a new timer initialized with the passed args if none exists yet. +**/ +fun getOrStartTimer (entity: Entity, identifier: String, vararg args: Any) : RSTimer? { + val existing = getTimer (entity, identifier) + if (existing != null) + return existing + return spawnTimer (identifier, *args).also { registerTimer (entity, it) } +} + +/** + * Used to fetch the existing, active, non-abstract and non-anonymous timer with the given type, or start a new timer if none exists and return that. + * @param entity the entity whose timer we're retrieving + * @param T the type of timer we are fetching + * @param args the various args to pass to the initialization of the timer, if applicable. + * @return Either the existing, active timer or a new timer initialized with the passed args if none exists yet. +**/ +inline fun getOrStartTimer (entity: Entity, vararg args: Any) : T { + val existing = getTimer (entity) + if (existing != null) + return existing + return spawnTimer (*args).also { registerTimer (entity, it) } +} + +/** + * Used to fetch a new instance of a registered (see: non-anonymous, non-abstract) timer with the given configuration args + * @param identifier the string identifier for the timer. e.g. poison's is "poison" + * @param args various arbitrary arguments to be passed to the timer's constructor. Refer to the timer in question for what the args are expected to be. + * @return a timer instance configured with your given args, or null if no timer with the given key exists in the registry. +**/ +fun spawnTimer (identifier: String, vararg args: Any) : RSTimer? { + return TimerRegistry.getTimerInstance (identifier, *args) +} + +/** + * Used to fetch a new instance of a registered (see: non-anonymous, non-abstract) timer with the given configuration args + * @param T the type of the timer you're trying to retrieve. The timer registry will be searched for a timer of this type. + * @param args various arbitrary arguments to be passed to the timer's constructor. Refer to the timer in question for what the args are expected to be. + * @return a timer instance configured with your given args, or null if the timer is not listed in the registry (if this happens, your timer is either abstract or anonymous.) +**/ +inline fun spawnTimer (vararg args: Any) : T { + return TimerRegistry.getTimerInstance (*args)!! +} + +/** + * Used to check if a timer of the given type is registered and active in the entity's timers. + * @param T the type of timer + * @param entity the entity whose timers are being checked + * @return true if there is a timer registered and active with the given type. +**/ +inline fun hasTimerActive (entity: Entity) : Boolean { + return getTimer(entity) != null +} + +/** + * Used to get the active instance of a timer with the given type from the entity's timers. + * @param T the type of timer + * @param entity the entity whose timers we are checking + * @return the active instance of the given type in the entity's timers, or null. +*/ +inline fun getTimer (entity: Entity) : T? { + return entity.timers.getTimer() +} + +/** + * Removes any active timers of the given type from the entity's active timers. This will remove ALL matching instances. + * @param T the type of timer + * @param entity the entity whose timers are being checked +**/ +inline fun removeTimer (entity: Entity) { + entity.timers.removeTimer() +} + +/** + * Used to check if a timer with the given identifier string is registered and active in the entity's timers. + * @param identifier the identifier token assigned to the timer. You'll have to refer to the actual timer class for this string. + * @param entity the entity whose timers are being checked + * @return true if there's a timer with the given identifier active in the entity's timers +**/ +fun hasTimerActive (entity: Entity, identifier: String) : Boolean { + return getTimer (entity, identifier) != null +} + +/** + * Used to get the active instance of a timer with the given identifier from the entity's timers. + * @param identifier the string identifier of the timer we are looking for. You'll have to refer to the actual timer class for this string. + * @param entity the entity whose timers are being checked + * @return the instance of the active timer if found, null otherwise. +**/ +fun getTimer (entity: Entity, identifier: String) : RSTimer? { + return entity.timers.getTimer(identifier) +} + +/** + * Removes any active timers with the given identifier from the entity's active timers. This will remove ALL matching instances. + * @param identifier the identifier token assigned to the timer. You'll have to refer to the actual timer class for this string. + * @param entity the entity whose timers are being checked +**/ +fun removeTimer (entity: Entity, identifier: String) { + entity.timers.removeTimer(identifier) +} + +/** + * Removes the given timer from the entity's active timers. Note this variant will only work with a reference to the same exact timer you want to remove. + * @param entity the entity whose timers we are modifying + * @param timer the timer to remove +**/ +fun removeTimer (entity: Entity, timer: RSTimer) { + entity.timers.removeTimer(timer) +} + /** * Locks the given entity for the given number of ticks * @param entity the entity to lock @@ -2492,37 +2618,6 @@ fun requireQuest(player: Player, questName: String, message: String) : Boolean { } -/** - * Determines whether or not a specified entity has a state - * @param entity the entity whose state we are checking - * @param state the state to check for - * @return whether or not the entity has the provided state - */ -fun hasState(entity: Entity, state: EntityState) : Boolean { - return entity.stateManager.hasState(state) -} - -/** - * Adds a state to the entity - * @param entity the entity whose state we are adding - * @param state the state to add - * @param override whether or not it's to override another state - */ -fun addState(entity: Entity, state: EntityState, override: Boolean, vararg args: Any?) { - if(!entity.stateManager.hasState(state)) { - entity.stateManager.register(state, override, *args) - } -} - -/** - * Removes a state from the entity - * @param entity the entity whose state we are removing - * @param state the state to remove - */ -fun removeState(entity: Entity, state: EntityState) { - entity.stateManager.remove(state) -} - /** * Determines whether or not specified node is a player * @param entity the node whom we are checking @@ -2690,6 +2785,36 @@ fun isStunned(entity: Entity) : Boolean { return entity.clocks[Clocks.STUN] >= getWorldTicks() } +/** + * Applies poison to the target. (In other words, creates and starts a poison timer.) + * @param entity the entity who will be receiving the poison damage. + * @param source the entity to whom credit for the damage should be awarded (the attacker.) You should award credit to the victim if the poison is sourceless (e.g. from a trap or plant or something) + * @param severity the severity of the poison damage. Severity is not a 1:1 representation of damage, rather the formula `floor((severity + 4) / 5)` is used. Severity is decreased by 1 with each application of the poison, and ends when it reaches 0. + * @see To those whe ask "why severity instead of plain damage?" to which the answer is: severity is how it works authentically, and allows for scenarios where, e.g. a poison should hit 6 once, and then drop to 5 immediately. +**/ +fun applyPoison (entity: Entity, source: Entity, severity: Int) { + val existingTimer = getTimer(entity) + + if (existingTimer != null) { + existingTimer.severity = severity + existingTimer.damageSource = source + } else { + registerTimer(entity, spawnTimer(source, severity)) + } +} + +fun isPoisoned (entity: Entity) : Boolean { + return getTimer(entity) != null +} + +fun curePoison (entity: Entity) { + if (!hasTimerActive(entity)) + return + removeTimer(entity) + if (entity is Player) + sendMessage(entity, "Your poison has been cured.") +} + fun setCurrentScriptState(entity: Entity, state: Int) { val script = entity.scripts.getActiveScript() if (script == null) { diff --git a/Server/src/main/core/api/Event.kt b/Server/src/main/core/api/Event.kt index bb315bc97..1c1bc1ea1 100644 --- a/Server/src/main/core/api/Event.kt +++ b/Server/src/main/core/api/Event.kt @@ -35,4 +35,6 @@ object Event { @JvmStatic val SummoningPointsRecharged = SummoningPointsRechargeEvent::class.java @JvmStatic val PrayerPointsRecharged = PrayerPointsRechargeEvent::class.java @JvmStatic val XpGained = XPGainEvent::class.java -} \ No newline at end of file + @JvmStatic val PrayerActivated = PrayerActivatedEvent::class.java + @JvmStatic val PrayerDeactivated = PrayerDeactivatedEvent::class.java +} diff --git a/Server/src/main/core/game/event/EventHook.kt b/Server/src/main/core/game/event/EventHook.kt index ce77835a2..728fa46b8 100644 --- a/Server/src/main/core/game/event/EventHook.kt +++ b/Server/src/main/core/game/event/EventHook.kt @@ -2,6 +2,6 @@ package core.game.event import core.game.node.entity.Entity -interface EventHook { +interface EventHook { fun process(entity: Entity, event: T) -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/event/Events.kt b/Server/src/main/core/game/event/Events.kt index bde021dc9..900b41629 100644 --- a/Server/src/main/core/game/event/Events.kt +++ b/Server/src/main/core/game/event/Events.kt @@ -7,6 +7,7 @@ import core.game.node.entity.Entity import core.game.node.entity.npc.NPC import core.game.node.entity.player.link.SpellBookManager.SpellBook import core.game.node.entity.player.link.TeleportManager.TeleportType +import core.game.node.entity.player.link.prayer.PrayerType import core.game.node.item.Item import core.game.world.map.Location import content.global.activity.jobs.JobType @@ -44,4 +45,6 @@ data class VarbitUpdateEvent(val offset: Int, val value: Int) : Event data class DynamicSkillLevelChangeEvent(val skillId: Int, val oldValue: Int, val newValue: Int): Event data class SummoningPointsRechargeEvent(val obelisk: Node) : Event data class PrayerPointsRechargeEvent(val altar: Node) : Event -data class XPGainEvent(val skillId: Int, val amount: Double) : Event \ No newline at end of file +data class XPGainEvent(val skillId: Int, val amount: Double) : Event +data class PrayerActivatedEvent (val type: PrayerType) : Event +data class PrayerDeactivatedEvent (val type: PrayerType) : Event diff --git a/Server/src/main/core/game/node/entity/Entity.java b/Server/src/main/core/game/node/entity/Entity.java index d6b7e4e26..255680cf8 100644 --- a/Server/src/main/core/game/node/entity/Entity.java +++ b/Server/src/main/core/game/node/entity/Entity.java @@ -15,8 +15,6 @@ import core.game.node.entity.npc.NPC; 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.node.entity.state.EntityState; -import core.game.node.entity.state.StateManager; import core.game.system.task.Pulse; import org.jetbrains.annotations.NotNull; import core.game.world.GameWorld; @@ -30,6 +28,8 @@ import core.game.world.update.flag.context.Graphics; import core.game.world.update.flag.*; import core.game.node.entity.combat.CombatSwingHandler; import core.game.world.update.UpdateMasks; +import core.game.system.timer.TimerManager; +import core.game.system.timer.TimerRegistry; import java.util.*; @@ -101,11 +101,6 @@ public abstract class Entity extends Node { */ private final ZoneMonitor zoneMonitor = new ZoneMonitor(this); - /** - * The state manager. - */ - private final StateManager stateManager = new StateManager(this); - /** * The reward locks. */ @@ -118,7 +113,7 @@ public abstract class Entity extends Node { * The mapping of event types to event hooks */ private HashMap, ArrayList> hooks = new HashMap<>(); - + public TimerManager timers = new TimerManager(this); /** * If the entity is invisible. @@ -209,6 +204,7 @@ public abstract class Entity extends Node { */ public void init() { active = true; + TimerRegistry.addAutoTimers (this); } /** @@ -221,6 +217,7 @@ public abstract class Entity extends Node { Location old = location != null ? location.transform(0, 0, 0) : Location.create(0,0,0); walkingQueue.update(); scripts.postMovement(!Objects.equals(location, old)); + timers.processTimers(); updateMasks.prepare(this); } @@ -261,6 +258,8 @@ public abstract class Entity extends Node { */ public void fullRestore() { skills.restore(); + timers.removeTimer("poison"); + timers.removeTimer("poison:immunity"); } /** @@ -279,7 +278,6 @@ public abstract class Entity extends Node { skills.rechargePrayerPoints(); impactHandler.getImpactQueue().clear(); impactHandler.setDisabledTicks(10); - stateManager.reset(); removeAttribute("combat-time"); face(null); //Check if it's a Loar shade and transform back into the shadow version. @@ -916,14 +914,6 @@ public abstract class Entity extends Node { return zoneMonitor; } - /** - * Gets the stateManager. - * @return The stateManager. - */ - public StateManager getStateManager() { - return stateManager; - } - /** * Checks if we have fire resistance. * @return {@code True} if so. @@ -937,7 +927,7 @@ public abstract class Entity extends Node { * @return {@code True} if so. */ public boolean isTeleBlocked() { - return stateManager.hasState(EntityState.TELEBLOCK); + return timers.getTimer("teleblock") != null; } /** diff --git a/Server/src/main/core/game/node/entity/combat/CombatPulse.kt b/Server/src/main/core/game/node/entity/combat/CombatPulse.kt index d294229da..99078ad34 100644 --- a/Server/src/main/core/game/node/entity/combat/CombatPulse.kt +++ b/Server/src/main/core/game/node/entity/combat/CombatPulse.kt @@ -11,12 +11,13 @@ import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.player.link.audio.Audio import core.game.node.entity.skill.Skills -import core.game.node.entity.state.EntityState import core.game.node.item.Item import core.game.system.task.Pulse import core.game.world.GameWorld import core.game.world.update.flag.context.Animation import core.tools.RandomFunction +import core.api.* +import core.game.system.timer.impl.* /** * The combat-handling pulse implementation. @@ -147,7 +148,7 @@ class CombatPulse( } else if (entity.properties.attackStyle.style == WeaponInterface.STYLE_RAPID || (salamander && entity.properties.attackStyle.style == WeaponInterface.STYLE_RANGE_ACCURATE)) { speed-- } - if (!magic && entity.stateManager.hasState(EntityState.MIASMIC)) { + if (!magic && hasTimerActive(entity)) { speed = (speed * 1.5).toInt() } setNextAttack(speed) diff --git a/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt b/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt index 5d1380423..7dddb9576 100644 --- a/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt +++ b/Server/src/main/core/game/node/entity/combat/MeleeSwingHandler.kt @@ -13,7 +13,6 @@ import core.game.node.entity.combat.equipment.WeaponInterface import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills -import core.game.node.entity.state.EntityState import core.game.world.map.path.Pathfinder import core.tools.RandomFunction import org.rs09.consts.Items @@ -119,7 +118,7 @@ open class MeleeSwingHandler damage = 48 } if (damage > -1 && RandomFunction.random(10) < 4) { - victim.stateManager.register(EntityState.POISONED, false, damage, entity) + applyPoison (victim, entity, damage) } } } diff --git a/Server/src/main/core/game/node/entity/combat/RangeSwingHandler.kt b/Server/src/main/core/game/node/entity/combat/RangeSwingHandler.kt index 665171f59..b4871c192 100644 --- a/Server/src/main/core/game/node/entity/combat/RangeSwingHandler.kt +++ b/Server/src/main/core/game/node/entity/combat/RangeSwingHandler.kt @@ -1,7 +1,7 @@ package core.game.node.entity.combat import content.global.skill.skillcapeperks.SkillcapePerks -import core.api.log +import core.api.* import core.game.container.impl.EquipmentContainer import core.game.node.entity.Entity import core.game.node.entity.combat.equipment.* @@ -10,7 +10,6 @@ import core.game.node.entity.impl.Projectile import core.game.node.entity.npc.NPC import core.game.node.entity.player.Player import core.game.node.entity.skill.Skills -import core.game.node.entity.state.EntityState import core.game.node.item.GroundItem import core.game.node.item.GroundItemManager import core.game.node.item.Item @@ -125,7 +124,7 @@ open class RangeSwingHandler if (state.ammunition != null && entity is Player) { val damage = state.ammunition.poisonDamage if (state.estimatedHit > 0 && damage > 8 && RandomFunction.random(10) < 4) { - victim.stateManager.register(EntityState.POISONED, false, damage, entity) + applyPoison (victim, entity, damage) } } super.adjustBattleState(entity, victim, state) diff --git a/Server/src/main/core/game/node/entity/combat/equipment/BoltEffect.java b/Server/src/main/core/game/node/entity/combat/equipment/BoltEffect.java index 7b6d5275b..207add03e 100644 --- a/Server/src/main/core/game/node/entity/combat/equipment/BoltEffect.java +++ b/Server/src/main/core/game/node/entity/combat/equipment/BoltEffect.java @@ -7,12 +7,13 @@ import core.game.node.entity.combat.ImpactHandler.HitsplatType; import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.audio.Audio; -import core.game.node.entity.state.EntityState; import core.game.world.GameWorld; import core.game.world.update.flag.context.Graphics; import core.tools.RandomFunction; import org.rs09.consts.NPCs; +import static core.api.ContentAPIKt.*; + /** * Represents a bolt effect. * @author Vexia @@ -107,7 +108,7 @@ public enum BoltEffect { EMERALD(9241, new Graphics(752), new Audio(2919)) { @Override public void impact(BattleState state) { - state.getVictim().getStateManager().register(EntityState.POISONED, false, 68, state.getAttacker()); + applyPoison(state.getVictim(), state.getAttacker(), 40); super.impact(state); } }, diff --git a/Server/src/main/core/game/node/entity/combat/equipment/FireType.java b/Server/src/main/core/game/node/entity/combat/equipment/FireType.java index d4d18d910..4e006d648 100644 --- a/Server/src/main/core/game/node/entity/combat/equipment/FireType.java +++ b/Server/src/main/core/game/node/entity/combat/equipment/FireType.java @@ -4,11 +4,12 @@ import core.game.node.Node; import core.game.node.entity.Entity; import core.game.node.entity.impl.Animator.Priority; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.game.system.task.NodeTask; import core.game.world.update.flag.context.Animation; import core.tools.RandomFunction; +import static core.api.ContentAPIKt.*; + /** * The fire types. * @author Emperor @@ -47,7 +48,7 @@ public enum FireType { TOXIC_BREATH(new Animation(82, Priority.HIGH), 394, new NodeTask(0) { @Override public boolean exec(Node node, Node... n) { - ((Entity) node).getStateManager().register(EntityState.POISONED, false, 80, (Entity) n[0]); + applyPoison ((Entity) node, (Entity) n[0], 40); return true; } }), @@ -58,7 +59,7 @@ public enum FireType { ICY_BREATH(new Animation(83, Priority.HIGH), 395, new NodeTask(0) { @Override public boolean exec(Node node, Node... n) { - ((Entity) node).getStateManager().set(EntityState.FROZEN, 7); + registerTimer((Entity) node, spawnTimer ("frozen", 7, true)); return true; } }); @@ -112,4 +113,4 @@ public enum FireType { return task; } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/node/entity/combat/spell/SpellType.java b/Server/src/main/core/game/node/entity/combat/spell/SpellType.java index 0f151f236..828a88804 100644 --- a/Server/src/main/core/game/node/entity/combat/spell/SpellType.java +++ b/Server/src/main/core/game/node/entity/combat/spell/SpellType.java @@ -5,9 +5,10 @@ import core.game.node.entity.skill.Skills; 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.state.EntityState; import core.game.node.item.Item; +import static core.api.ContentAPIKt.*; + /** * Represents the spell types. * @author Emperor @@ -152,7 +153,7 @@ public enum SpellType { @Override public int getImpactAmount(Entity e, Entity victim, int base) { if(!(e instanceof Player)) return 20; - if (e.asPlayer().hasActiveState("godcharge")) { + if (hasTimerActive(e, "magic:spellcharge")) { Item cape = ((Player) e).getEquipment().getNew(EquipmentContainer.SLOT_CAPE); if (cape.getId() == 2412 || cape.getId() == 2413 || cape.getId() == 2414) { return 30; @@ -252,4 +253,4 @@ public enum SpellType { return accuracyMod; } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/node/entity/impl/WalkingQueue.java b/Server/src/main/core/game/node/entity/impl/WalkingQueue.java index 6a5dbcf48..eaf9604dc 100644 --- a/Server/src/main/core/game/node/entity/impl/WalkingQueue.java +++ b/Server/src/main/core/game/node/entity/impl/WalkingQueue.java @@ -18,7 +18,7 @@ import java.util.Deque; import java.util.ArrayDeque; import java.util.ArrayList; -import static core.api.ContentAPIKt.log; +import static core.api.ContentAPIKt.*; /** * The walking queue. @@ -89,6 +89,8 @@ public final class WalkingQueue { if (isPlayer && updateRegion(entity.getLocation(), true)) { return; } + if (hasTimerActive(entity, "frozen")) + return; Point point = walkingQueue.poll(); boolean drawPath = entity.getAttribute("routedraw", false); if (point == null) { diff --git a/Server/src/main/core/game/node/entity/npc/NPC.java b/Server/src/main/core/game/node/entity/npc/NPC.java index 9c1d0dc6f..71071cf64 100644 --- a/Server/src/main/core/game/node/entity/npc/NPC.java +++ b/Server/src/main/core/game/node/entity/npc/NPC.java @@ -424,6 +424,7 @@ public class NPC extends Entity { if (isRespawning && respawnTick <= GameWorld.getTicks()) { behavior.onRespawn(this); onRespawn(); + fullRestore(); isRespawning = false; } handleTickActions(); 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 15b6e9e82..0904ec16d 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -679,6 +679,8 @@ public class Player extends Entity { getPrayer().reset(); super.finalizeDeath(killer); appearance.sync(); + timers.removeTimer("poison"); + timers.removeTimer("poison:immunity"); if (!getSavedData().getGlobalData().isDeathScreenDisabled()) { getInterfaceManager().open(new Component(153)); } @@ -747,7 +749,7 @@ public class Player extends Entity { @Override public boolean isPoisonImmune() { - return getAttribute("poison:immunity", -1) > GameWorld.getTicks(); + return timers.getTimer("poison:immunity") != null; } @Override diff --git a/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java b/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java index 534526a2f..15bea7a02 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java +++ b/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java @@ -139,7 +139,6 @@ public final class LoginConfiguration { player.getMusicPlayer().init(); player.updateAppearance(); player.getPlayerFlags().setUpdateSceneGraph(true); - player.getStateManager().init(); player.getPacketDispatch().sendInterfaceConfig(226, 1, true); if(player.getGlobalData().getTestStage() == 3 && !player.getEmoteManager().isUnlocked(Emotes.SAFETY_FIRST)){ player.getEmoteManager().unlock(Emotes.SAFETY_FIRST); 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 d89a67582..b28353be4 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 @@ -299,6 +299,7 @@ class PlayerSaveParser(val player: Player) { val bOre = coreData["blastOre"] as? JSONArray val bCoal = coreData["blastCoal"] as? JSONArray val varpData = coreData["varp"] as? JSONArray + val timerData = coreData["timers"] as? JSONObject val location = coreData["location"] as String val bankTabData = coreData["bankTabs"] if (bankTabData != null) { @@ -342,6 +343,9 @@ class PlayerSaveParser(val player: Player) { player.saveVarp[index] = true } } + + if (timerData != null) + player.timers.parseTimers(timerData) } fun parseSkills() { 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 7127babf3..b85e58a2e 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 @@ -20,8 +20,8 @@ import java.io.File import java.io.FileWriter import java.io.IOException import java.lang.Math.ceil -import java.util.* import javax.script.ScriptEngineManager +import java.util.* /** @@ -644,6 +644,7 @@ class PlayerSaver (val player: Player){ val varpData = JSONArray() for ((index, value) in player.varpMap) { if (!(player.saveVarp[index] ?: false)) continue + if (value == 0) continue val varpObj = JSONObject() varpObj["index"] = index.toString() @@ -652,6 +653,10 @@ class PlayerSaver (val player: Player){ } coreData.put("varp", varpData) + val timerData = JSONObject() + player.timers.saveTimers(timerData) + coreData.put("timers", timerData) + root.put("core_data",coreData) } } diff --git a/Server/src/main/core/game/node/entity/player/link/Settings.java b/Server/src/main/core/game/node/entity/player/link/Settings.java index a713bdfbd..cd02c0b03 100644 --- a/Server/src/main/core/game/node/entity/player/link/Settings.java +++ b/Server/src/main/core/game/node/entity/player/link/Settings.java @@ -147,22 +147,6 @@ public final class Settings { setVarp(player, 43, attackStyleIndex); player.getPacketDispatch().sendRunEnergy(); updateChatSettings(); - Pulse pulse = player.getAttribute("energy-restore", null); - if (pulse == null || !pulse.isRunning()) { - pulse = new Pulse(50, player) { - @Override - public boolean pulse() { - if (specialEnergy < 100) { - int heal = 100 - specialEnergy; - setSpecialEnergy(specialEnergy + (heal > 10 ? 10 : heal)); - } - return false; - } - }; - pulse.setTicksPassed(1); - GameWorld.getPulser().submit(pulse); - player.setAttribute("energy-restore", pulse); - } } /** diff --git a/Server/src/main/core/game/node/entity/player/link/SkullManager.java b/Server/src/main/core/game/node/entity/player/link/SkullManager.java index de4d4161c..3732745bb 100644 --- a/Server/src/main/core/game/node/entity/player/link/SkullManager.java +++ b/Server/src/main/core/game/node/entity/player/link/SkullManager.java @@ -2,8 +2,8 @@ package core.game.node.entity.player.link; import core.game.node.entity.Entity; import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; import core.ServerConstants; +import static core.api.ContentAPIKt.*; import java.util.ArrayList; import java.util.List; @@ -81,8 +81,8 @@ public final class SkullManager { return; } skullCauses.add(o); - player.clearState("skull"); - player.registerState("skull").init(); + removeTimer (player, "skulled"); + registerTimer (player, spawnTimer("skulled", 2000)); } /** diff --git a/Server/src/main/core/game/node/entity/player/link/prayer/PrayerType.java b/Server/src/main/core/game/node/entity/player/link/prayer/PrayerType.java index fc64e0ccf..26bc87086 100644 --- a/Server/src/main/core/game/node/entity/player/link/prayer/PrayerType.java +++ b/Server/src/main/core/game/node/entity/player/link/prayer/PrayerType.java @@ -2,12 +2,12 @@ package core.game.node.entity.player.link.prayer; import core.game.node.entity.player.link.diary.DiaryType; import core.game.node.entity.skill.SkillBonus; -import core.game.node.entity.skill.SkillRestoration; import core.game.node.entity.skill.Skills; import core.game.node.entity.player.Player; import core.game.node.entity.player.link.audio.Audio; import core.game.world.map.zone.ZoneBorders; import core.tools.StringUtils; +import core.game.event.*; import java.util.List; @@ -245,9 +245,11 @@ public enum PrayerType { && new ZoneBorders(2732, 3467, 2739, 3471, 0).insideBorder(player)) { player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 2, 3); } + player.dispatch (new PrayerActivatedEvent(this)); } else { player.getPrayer().getActive().remove(this); findNextIcon(player); + player.dispatch (new PrayerDeactivatedEvent(this)); } return true; } diff --git a/Server/src/main/core/game/node/entity/skill/SkillRestoration.java b/Server/src/main/core/game/node/entity/skill/SkillRestoration.java deleted file mode 100644 index af10b0eb7..000000000 --- a/Server/src/main/core/game/node/entity/skill/SkillRestoration.java +++ /dev/null @@ -1,83 +0,0 @@ -package core.game.node.entity.skill; - -import core.game.container.impl.EquipmentContainer; -import core.game.node.entity.Entity; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.prayer.PrayerType; -import core.game.node.item.Item; -import org.rs09.consts.Items; -import core.game.world.GameWorld; - -/** - * Handles the skill restoration data. - * @author Emperor - */ -public final class SkillRestoration { - - /** - * The skill index. - */ - private final int skillId; - - /** - * Constructs a new {@code SkillRestoration} {@code Object}. - * @param skillId The skill id. - */ - public SkillRestoration(int skillId) { - this.skillId = skillId; - } - - /** - * Restores the skill. - * @param entity The entity. - */ - public void restore(Entity entity) { - Skills skills = entity.getSkills(); - int max = skills.getStaticLevel(skillId); - int tickDivisor = 1; - if(skillId == Skills.HITPOINTS){ - max = skills.getMaximumLifepoints(); - if(entity instanceof Player) { - Player player = entity.asPlayer(); - if(player.getPrayer().getActive().contains(PrayerType.RAPID_HEAL)) { - tickDivisor *= 2; - } - Item gloves = player.getEquipment().get(EquipmentContainer.SLOT_HANDS); - if(gloves != null && gloves.getId() == Items.REGEN_BRACELET_11133) { - tickDivisor *= 2; - } - } - } - if(skillId == Skills.PRAYER) { - return; - } - if(skillId != Skills.HITPOINTS && skillId != Skills.SUMMONING) { - if(entity instanceof Player) { - if(entity.asPlayer().getPrayer().getActive().contains(PrayerType.RAPID_RESTORE)) { - tickDivisor *= 2; - } - } - } - - int ticks = 100 / tickDivisor; - if(GameWorld.getTicks() % ticks == 0){ - if(skillId == Skills.HITPOINTS){ - if(skills.getLifepoints() >= max){ - return; - } - skills.heal(1); - } else { - int current = skills.getLevel(skillId); - skills.updateLevel(skillId,current < max ? 1 : -1,max); - } - } - } - - /** - * Gets the skillId. - * @return The skillId. - */ - public int getSkillId() { - return skillId; - } -} 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 f1b67f7fc..eafb208d9 100644 --- a/Server/src/main/core/game/node/entity/skill/Skills.java +++ b/Server/src/main/core/game/node/entity/skill/Skills.java @@ -104,11 +104,6 @@ public final class Skills { */ private double experienceGained = 0; - /** - * The restoration pulses. - */ - private final SkillRestoration[] restoration; - /** * If a lifepoints update should occur. */ @@ -135,7 +130,6 @@ public final class Skills { this.experience = new double[24]; this.staticLevels = new int[24]; this.dynamicLevels = new int[24]; - this.restoration = new SkillRestoration[24]; for (int i = 0; i < 24; i++) { this.staticLevels[i] = 1; this.dynamicLevels[i] = 1; @@ -165,15 +159,6 @@ public final class Skills { */ public void configure() { updateCombatLevel(); - int max = 24; - if (entity instanceof NPC) { - max = 7; - } - for (int i = 0; i < max; i++) { - if (i != PRAYER && i != SUMMONING && restoration[i] == null) { - configureRestorationPulse(i); - } - } } /** @@ -183,19 +168,6 @@ public final class Skills { if (lifepoints < 1) { return; } - for (int i = 0; i < restoration.length; i++) { - if (restoration[i] != null) { - restoration[i].restore(entity); - } - } - } - - /** - * Configures a restoration pulse for the given skill id. - * @param skillId The skill id. - */ - private void configureRestorationPulse(final int skillId) { - restoration[skillId] = new SkillRestoration(skillId); } /** @@ -883,14 +855,6 @@ public final class Skills { } } - /** - * Gets the restoration pulses. - * @return The restoration pulse array. - */ - public SkillRestoration[] getRestoration() { - return restoration; - } - /** * Gets the amount of mastered skills. * @return The amount of mastered skills. diff --git a/Server/src/main/core/game/node/entity/state/EntityState.java b/Server/src/main/core/game/node/entity/state/EntityState.java deleted file mode 100644 index e75866246..000000000 --- a/Server/src/main/core/game/node/entity/state/EntityState.java +++ /dev/null @@ -1,66 +0,0 @@ -package core.game.node.entity.state; - -import core.game.node.entity.state.impl.*; - -/** - * Represents the statuses. - * @author Emperor - */ -public enum EntityState { - - /** - * The entity is poisoned. - */ - POISONED(new PoisonStatePulse(null)), - - /** - * The entity is stunned. - */ - STUNNED(new StunStatePulse(null, 0)), - - /** - * The entity is frozen. - */ - FROZEN(new FrozenStatePulse(null, 0)), - - /** - * The entity is skulled. - */ - SKULLED(new SkullStatePulse(null, 0)), - - /** - * The entity is under teleblock. - */ - TELEBLOCK(new TeleblockStatePulse(null, 0, 0)), - - /** - * The entity has decreased weapon speeds. - */ - MIASMIC(new MiasmicStatePulse(null, 0)), - - /** - * The entity is healing over time - */ - HEALOVERTIME(new HealOverTimePulse(null,0,0,0,0)); - - /** - * The state pulse used for this state. - */ - private final StatePulse pulse; - - /** - * Constructs a new {@code EntityState} {@code Object}. - * @param pulse The state pulse. - */ - private EntityState(StatePulse pulse) { - this.pulse = pulse; - } - - /** - * Gets the pulse. - * @return The pulse. - */ - public StatePulse getPulse() { - return pulse; - } -} \ No newline at end of file diff --git a/Server/src/main/core/game/node/entity/state/StateManager.java b/Server/src/main/core/game/node/entity/state/StateManager.java deleted file mode 100644 index 304b1cee0..000000000 --- a/Server/src/main/core/game/node/entity/state/StateManager.java +++ /dev/null @@ -1,138 +0,0 @@ -package core.game.node.entity.state; - -import core.game.node.entity.Entity; - - -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; - -/** - * Handles an entity's status (eg. poisoned, stunned, frozen, skulled, ...) - * @author Emperor - */ -public final class StateManager { - - /** - * The entity. - */ - private final Entity entity; - - /** - * The entity's current states. - */ - private final Map states = new HashMap<>(); - - /** - * Constructs a new {@code StateManager} {@code Object}. - * @param entity The entity. - */ - public StateManager(Entity entity) { - this.entity = entity; - } - - /** - * Initializes the pulses. - */ - public void init() { - for (StatePulse pulse : states.values()) { - pulse.run(); - } - } - - /** - * Checks if a save is required. - * @return {@code True} if so. - */ - public boolean isSaveRequired() { - return !states.isEmpty(); - } - - /** - * Registers a state. - * @param state The state. - * @param args The arguments. - */ - public void set(EntityState state, Object... args) { - register(state, true, args); - } - - /** - * Registers a state. - * @param state The state. - * @param override If the previous pulse (if any) should be overriden. - * @param args The arguments. - */ - public void register(EntityState state, boolean override, Object... args) { - if (!state.getPulse().canRun(entity)) { - return; - } - StatePulse pulse = states.get(state); - if (pulse != null) { - if (!override) { - return; - } - pulse.stop(); - } - pulse = state.getPulse().create(entity, args); - pulse.run(); - states.put(state, pulse); - } - - /** - * Resets all the states. - */ - public void reset() { - for (StatePulse pulse : states.values()) { - if (pulse.isRunning()) { - pulse.remove(); - pulse.stop(); - } - } - states.clear(); - } - - /** - * Resets the state pulse. - * @param state The state. - */ - public void remove(EntityState state) { - StatePulse pulse = states.get(state); - if (pulse != null && pulse.isRunning()) { - pulse.remove(); - pulse.stop(); - } - states.remove(state); - } - - /** - * Checks if the entity has a pulse running for the given state. - * @param state The state. - * @return {@code True} if so. - */ - public boolean hasState(EntityState state) { - StatePulse pulse = states.get(state); - return pulse != null && pulse.isRunning(); - } - - /** - * Gets the pulse for the given state. - * @param state The state to get the pulse for. - * @return The state pulse. - */ - public StatePulse get(EntityState state) { - return states.get(state); - } - - /** - * Gets the entity. - * @return The entity. - */ - public Entity getEntity() { - return entity; - } - - public Map getStates() { - return states; - } -} \ No newline at end of file diff --git a/Server/src/main/core/game/node/entity/state/StateRepository.kt b/Server/src/main/core/game/node/entity/state/StateRepository.kt index 2cc7fc3b3..8e437e6f4 100644 --- a/Server/src/main/core/game/node/entity/state/StateRepository.kt +++ b/Server/src/main/core/game/node/entity/state/StateRepository.kt @@ -28,7 +28,7 @@ class StateRepository : StartupListener{ fun forKey(key: String, player: Player): State?{ val state = states[key] if(player.hasActiveState(key)){ - return null + return states[key] } if(state != null){ val clazz = state.newInstance(player) diff --git a/Server/src/main/core/game/node/entity/state/impl/FrozenStatePulse.java b/Server/src/main/core/game/node/entity/state/impl/FrozenStatePulse.java deleted file mode 100644 index ac22619e9..000000000 --- a/Server/src/main/core/game/node/entity/state/impl/FrozenStatePulse.java +++ /dev/null @@ -1,95 +0,0 @@ -package core.game.node.entity.state.impl; - -import core.game.node.entity.Entity; -import core.game.node.entity.player.Player; -import core.game.node.entity.state.StatePulse; -import core.game.world.GameWorld; - -import java.nio.ByteBuffer; - -/** - * Handles the frozen state pulse. - * @author Emperor - */ -public final class FrozenStatePulse extends StatePulse { - - /** - * The amount of ticks immunity lasts. - */ - private static final int IMMUNITY_TICK = 7; - - /** - * The message when frozen. - */ - private final String message; - - /** - * Constructs a new {@code FrozenStatePulse} {@code Object}. - * @param entity The entity. - * @param ticks The ticks to freeze for. - */ - public FrozenStatePulse(Entity entity, String message, int ticks) { - super(entity, ticks); - this.message = message; - } - - /** - * Constructs a new {@code FrozenStatePulse} {@code Object}. - * @param entity The entity. - * @param ticks The ticks to freeze for. - */ - public FrozenStatePulse(Entity entity, int ticks) { - this(entity, "You have been frozen!", ticks); - } - - @Override - public boolean canRun(Entity entity) { - return entity.getAttribute("freeze_immunity", -1) < GameWorld.getTicks(); - } - - @Override - public void start() { - super.start(); - if (entity.getPulseManager().isMovingPulse()) { - entity.getPulseManager().clear(); - } - entity.getWalkingQueue().reset(); - entity.getLocks().lockMovement(getDelay()); - entity.setAttribute("freeze_immunity", GameWorld.getTicks() + getDelay() + IMMUNITY_TICK); - if (entity instanceof Player) { - ((Player) entity).getPacketDispatch().sendMessage(message); - } - } - - @Override - public StatePulse create(Entity entity, Object... args) { - if (args.length > 1) { - return new FrozenStatePulse(entity, (String) args[1], (Integer) args[0]); - } else { - return new FrozenStatePulse(entity, (Integer) args[0]); - } - } - - @Override - public boolean pulse() { - return true; - } - - @Override - public boolean isSaveRequired() { - return false; - } - - @Override - public void save(ByteBuffer buffer) { - /* - * empty. - */ - } - - @Override - public StatePulse parse(Entity entity, ByteBuffer buffer) { - return null; - } - -} diff --git a/Server/src/main/core/game/node/entity/state/impl/HealOverTimePulse.java b/Server/src/main/core/game/node/entity/state/impl/HealOverTimePulse.java deleted file mode 100644 index dba94089e..000000000 --- a/Server/src/main/core/game/node/entity/state/impl/HealOverTimePulse.java +++ /dev/null @@ -1,86 +0,0 @@ -package core.game.node.entity.state.impl; - -import core.game.node.entity.Entity; -import core.game.node.entity.player.Player; -import core.game.node.entity.state.StatePulse; -import core.game.world.update.flag.context.Graphics; - -import java.nio.ByteBuffer; - -/** - * Method for healing a player a certain amount of HP - * every X amount of ticks for a total of X amount of heals - * over X amount of ticks. - * - * @author phil lips - */ - -public class HealOverTimePulse extends StatePulse { - - /**The total amount of HP to heal*/ - - private int totalToHeal; - - /**How many ticks to spread the heals over*/ - - private int ticksTotal; - - /**How many times to heal the player during those ticks*/ - - private int timesToHeal; - - private int currentTick; - - /** - * The entity. - */ - protected final Entity entity; - - /**Constructs a new HealOverTimePulse object - * @param entity the entity to heal over time - * @param ticks the total time to spread the heal over - * @param totalHeal the total amount to heal for - * @param healInc how many times the total amount to heal is divided to*/ - - public HealOverTimePulse(Entity entity, int ticks, int totalHeal, int healInc, int currentTick){ - super(entity,0); - this.entity = entity; - this.ticksTotal = ticks; - this.totalToHeal = totalHeal; - this.timesToHeal = healInc; - this.currentTick = currentTick; - } - - @Override - public StatePulse create(Entity entity, Object... args) { - return new HealOverTimePulse(entity, (Integer) args[1],(Integer) args[2],(Integer) args[3],1); - } - - /** Checks if it can heal the player every pulse - * the mod is funny math haha :) - */ - @Override - public boolean pulse() { - if(currentTick != 0){ - if(currentTick % (ticksTotal / timesToHeal) == 0){ - entity.getSkills().heal(totalToHeal / timesToHeal); - } - } - currentTick += 1; - return currentTick > ticksTotal; - } - - @Override - public boolean isSaveRequired() { - return true; - } - - @Override - public void save(ByteBuffer buffer) { - } - - @Override - public StatePulse parse(Entity entity, ByteBuffer buffer) { - return null; - } -} diff --git a/Server/src/main/core/game/node/entity/state/impl/MiasmicStatePulse.java b/Server/src/main/core/game/node/entity/state/impl/MiasmicStatePulse.java deleted file mode 100644 index 628934bd8..000000000 --- a/Server/src/main/core/game/node/entity/state/impl/MiasmicStatePulse.java +++ /dev/null @@ -1,90 +0,0 @@ -package core.game.node.entity.state.impl; - -import core.game.node.entity.Entity; -import core.game.node.entity.player.Player; -import core.game.node.entity.state.StatePulse; -import core.game.world.GameWorld; - -import java.nio.ByteBuffer; - -/** - * Handles the Miasmic state pulse. - * @author Emperor - */ -public final class MiasmicStatePulse extends StatePulse { - - /** - * The amount of ticks immunity lasts. - */ - private static final int IMMUNITY_TICK = 7; - - /** - * The message when frozen. - */ - private final String message; - - /** - * Constructs a new {@code MiasmicStatePulse} {@code Object}. - * @param entity The entity. - * @param ticks The ticks to freeze for. - */ - public MiasmicStatePulse(Entity entity, String message, int ticks) { - super(entity, ticks); - this.message = message; - } - - /** - * Constructs a new {@code MiasmicStatePulse} {@code Object}. - * @param entity The entity. - * @param ticks The ticks to freeze for. - */ - public MiasmicStatePulse(Entity entity, int ticks) { - this(entity, null, ticks); - } - - @Override - public boolean canRun(Entity entity) { - return entity.getAttribute("miasmic_immunity", -1) < GameWorld.getTicks(); - } - - @Override - public void start() { - super.start(); - entity.setAttribute("miasmic_immunity", GameWorld.getTicks() + getDelay() + IMMUNITY_TICK); - if (entity instanceof Player) { - ((Player) entity).getPacketDispatch().sendMessage(message); - } - } - - @Override - public StatePulse create(Entity entity, Object... args) { - if (args.length > 1) { - return new MiasmicStatePulse(entity, (String) args[1], (Integer) args[0]); - } else { - return new MiasmicStatePulse(entity, (Integer) args[0]); - } - } - - @Override - public boolean pulse() { - return true; - } - - @Override - public boolean isSaveRequired() { - return false; - } - - @Override - public void save(ByteBuffer buffer) { - /* - * empty. - */ - } - - @Override - public StatePulse parse(Entity entity, ByteBuffer buffer) { - return null; - } - -} \ No newline at end of file diff --git a/Server/src/main/core/game/node/entity/state/impl/PoisonStatePulse.java b/Server/src/main/core/game/node/entity/state/impl/PoisonStatePulse.java deleted file mode 100644 index 26594fb9c..000000000 --- a/Server/src/main/core/game/node/entity/state/impl/PoisonStatePulse.java +++ /dev/null @@ -1,120 +0,0 @@ -package core.game.node.entity.state.impl; - -import core.game.node.entity.Entity; -import core.game.node.entity.combat.ImpactHandler; -import core.game.node.entity.player.Player; -import core.game.node.entity.state.StatePulse; - -import java.nio.ByteBuffer; - -/** - * Handles the poisoned state. - * @author Emperor - */ -public final class PoisonStatePulse extends StatePulse { - - /** - * The current amount of poison damage. - */ - private int damage; - - /** - * The entity poisoning this entity. - */ - private Entity poisoner; - - /** - * Constructs a new {@code PoisonStatePulse} {@code Object}. - * @param entity The entity. - */ - public PoisonStatePulse(Entity entity) { - super(entity, 30); - } - - @Override - public boolean canRun(Entity entity) { - return !entity.isPoisonImmune(); - } - - @Override - public void start() { - super.start(); - if (entity instanceof Player) { - ((Player) entity).getPacketDispatch().sendMessage("You have been poisoned!"); - } - } - - @Override - public boolean pulse() { - if (!poisoner.isActive()) { - poisoner = entity; - } - if (damage / 10 > 0) { - entity.getImpactHandler().manualHit(poisoner, damage / 10, ImpactHandler.HitsplatType.POISON); - } - damage -= 2; - if (damage < 10) { - if (entity instanceof Player) { - ((Player) entity).getPacketDispatch().sendMessage("The poison has wore off."); - } - return true; - } - return false; - } - - @Override - public boolean isSaveRequired() { - return damage > 9; - } - - @Override - public void save(ByteBuffer buffer) { - buffer.put((byte) damage); - } - - @Override - public StatePulse parse(Entity entity, ByteBuffer buffer) { - return create(entity, buffer.get() & 0xFF, entity); - } - - @Override - public StatePulse create(Entity entity, Object... args) { - PoisonStatePulse pulse = new PoisonStatePulse(entity); - pulse.damage = (Integer) args[0]; - pulse.poisoner = (Entity) args[1]; - return pulse; - } - - /** - * Gets the damage. - * @return The damage. - */ - public int getDamage() { - return damage; - } - - /** - * Sets the damage. - * @param damage The damage to set. - */ - public void setDamage(int damage) { - this.damage = damage; - } - - /** - * Gets the poisoner. - * @return The poisoner. - */ - public Entity getPoisoner() { - return poisoner; - } - - /** - * Sets the poisoner. - * @param poisoner The poisoner to set. - */ - public void setPoisoner(Entity poisoner) { - this.poisoner = poisoner; - } - -} \ No newline at end of file diff --git a/Server/src/main/core/game/node/entity/state/impl/SkullStatePulse.java b/Server/src/main/core/game/node/entity/state/impl/SkullStatePulse.java deleted file mode 100644 index 9946b3fbb..000000000 --- a/Server/src/main/core/game/node/entity/state/impl/SkullStatePulse.java +++ /dev/null @@ -1,70 +0,0 @@ -package core.game.node.entity.state.impl; - -import core.game.node.entity.Entity; -import core.game.node.entity.player.Player; -import core.game.node.entity.state.StatePulse; - -import java.nio.ByteBuffer; - -/** - * Handles the skulled state. - * @author Emperor - */ -public final class SkullStatePulse extends StatePulse { - - /** - * The amount of ticks. - */ - public static final int TICKS = 2000; - - /** - * Constructs a new {@code SkullStatePulse} {@code Object}. - * @param entity The entity. - * @param ticks The amount of ticks. - */ - public SkullStatePulse(Entity entity, int ticks) { - super(entity, ticks); - } - - @Override - public void start() { - super.start(); - Player player = (Player) entity; - player.getSkullManager().setSkullIcon(0); - player.getSkullManager().setSkulled(true); - } - - @Override - public boolean isSaveRequired() { - return getTicksPassed() < getDelay(); - } - - @Override - public void save(ByteBuffer buffer) { - buffer.putShort((short) (getDelay() - getTicksPassed())); - } - - @Override - public StatePulse parse(Entity entity, ByteBuffer buffer) { - return create(entity, buffer.getShort() & 0xFFFF); - } - - @Override - public StatePulse create(Entity entity, Object... args) { - int ticks = args.length > 0 ? (Integer) args[0] : TICKS; - return new SkullStatePulse(entity, ticks); - } - - @Override - public boolean pulse() { - setTicksPassed(getDelay()); - remove(); - return true; - } - - @Override - public void remove() { - ((Player) entity).getSkullManager().reset(); - } - -} \ No newline at end of file diff --git a/Server/src/main/core/game/node/entity/state/impl/StunStatePulse.java b/Server/src/main/core/game/node/entity/state/impl/StunStatePulse.java deleted file mode 100644 index 8f0827a61..000000000 --- a/Server/src/main/core/game/node/entity/state/impl/StunStatePulse.java +++ /dev/null @@ -1,96 +0,0 @@ -package core.game.node.entity.state.impl; - -import core.game.node.entity.Entity; -import core.game.node.entity.impl.Animator; -import core.game.node.entity.player.Player; -import core.game.node.entity.player.link.audio.Audio; -import core.game.node.entity.state.StatePulse; -import core.game.world.update.flag.context.Animation; -import core.game.world.update.flag.context.Graphics; - -import java.nio.ByteBuffer; - -/** - * Handles the stunned state. - * @author Emperor - */ -public final class StunStatePulse extends StatePulse { - - /** - * The stun graphic. - */ - private static final Graphics STUN_GRAPHIC = new Graphics(80, 96); - - private static final Audio STUN_AUDIO = new Audio(2727, 1, 0); - - private static final Animation STUN_ANIM = new Animation(424, Animator.Priority.VERY_HIGH); - - /** - * The stun message. - */ - private String stunMessage; - - /** - * Constructs a new {@code FrozenStatePulse} {@code Object}. - * @param entity The entity. - * @param ticks The ticks to freeze for. - * @param stunMessage The stun message. - */ - public StunStatePulse(Entity entity, int ticks, String stunMessage) { - super(entity, ticks); - this.stunMessage = stunMessage; - } - - /** - * Constructs a new @{Code StunStatePulse} object. - * @param entity The entity. - * @param ticks The ticks to freeze for. - */ - public StunStatePulse(Entity entity, int ticks) { - this(entity, ticks, "You have been stunned!"); - } - - @Override - public void start() { - super.start(); - entity.getWalkingQueue().reset(); - entity.getLocks().lock(getDelay()); - entity.graphics(STUN_GRAPHIC); - if (entity instanceof Player) { - entity.asPlayer().getAudioManager().send(STUN_AUDIO); - entity.animate(STUN_ANIM); - ((Player) entity).getPacketDispatch().sendMessage(stunMessage); - } - } - - @Override - public StatePulse create(Entity entity, Object... args) { - return new StunStatePulse(entity, (Integer) args[0], args.length > 1 ? (String) args[1] : "You have been stunned!"); - } - - @Override - public boolean pulse() { - if (entity.getAnimator().getGraphics() == STUN_GRAPHIC) { - entity.graphics(Graphics.create(-1)); - } - return true; - } - - @Override - public boolean isSaveRequired() { - return false; - } - - @Override - public void save(ByteBuffer buffer) { - /* - * empty. - */ - } - - @Override - public StatePulse parse(Entity entity, ByteBuffer buffer) { - return null; - } - -} \ No newline at end of file diff --git a/Server/src/main/core/game/node/entity/state/impl/TeleblockStatePulse.java b/Server/src/main/core/game/node/entity/state/impl/TeleblockStatePulse.java deleted file mode 100644 index 21f9e97cf..000000000 --- a/Server/src/main/core/game/node/entity/state/impl/TeleblockStatePulse.java +++ /dev/null @@ -1,105 +0,0 @@ -package core.game.node.entity.state.impl; - -import core.api.PersistPlayer; -import core.game.node.entity.Entity; -import core.game.node.entity.player.Player; -import core.game.node.entity.state.EntityState; -import core.game.node.entity.state.StatePulse; -import org.jetbrains.annotations.NotNull; -import org.json.simple.JSONObject; - -import java.nio.ByteBuffer; - -/** - * Handles the teleblock state pulse. - * @author Vexia - */ -public final class TeleblockStatePulse extends StatePulse implements PersistPlayer { - - /** - * The ticks needed to pass. - */ - public int ticks; - - /** - * The current tick. - */ - public int currentTick; - - /** - * Constructs a new {@Code TeleblockStatePulse} {@Code Object} - * @param entity the entity. - * @param ticks the ticks. - * @param currentTick the current tick. - */ - public TeleblockStatePulse(Entity entity, int ticks, int currentTick) { - super(entity, 1); - this.ticks = ticks; - this.currentTick = currentTick; - } - - //Required to be instantiated as a ContentListener for PersistPlayer - public TeleblockStatePulse() { - super(null, 0); - } - - @Override - public void savePlayer(@NotNull Player player, @NotNull JSONObject save) { - TeleblockStatePulse tbPulse = (TeleblockStatePulse) player.getStateManager().get(EntityState.TELEBLOCK); - - if (tbPulse != null && tbPulse.isSaveRequired()) { - JSONObject tbObj = new JSONObject(); - tbObj.put("tb-state-current", "" + tbPulse.currentTick); - tbObj.put("tb-state-total", "" + tbPulse.ticks); - save.put("teleblock-state", tbObj); - } - } - - @Override - public void parsePlayer(@NotNull Player player, @NotNull JSONObject data) { - if (data.containsKey("teleblock-state")) { - JSONObject tbData = (JSONObject) data.get("teleblock-state"); - int currentTick = Integer.parseInt(tbData.get("tb-state-current").toString()); - int totalTicks = Integer.parseInt(tbData.get("tb-state-total").toString()); - player.getStateManager().set(EntityState.TELEBLOCK, totalTicks, currentTick); - } - } - - @Override - public boolean isSaveRequired() { - return currentTick < ticks; - } - - @Override - public void save(ByteBuffer buffer) { - buffer.putInt(ticks); - buffer.putInt(currentTick); - } - - @Override - public StatePulse parse(Entity entity, ByteBuffer buffer) { - return new TeleblockStatePulse(entity, buffer.getInt(), buffer.getInt()); - } - - @Override - public void start() { - if (currentTick == 0) { - if (entity instanceof Player) { - entity.asPlayer().getAudioManager().send(203, true); - entity.asPlayer().sendMessage("You have been teleblocked."); - } - } - super.start(); - } - - @Override - public boolean pulse() { - return ++currentTick >= ticks; - } - - @Override - public StatePulse create(Entity entity, Object... args) { - return new TeleblockStatePulse(entity, (int) args[0], args.length > 1 ? (int) args[1] : 0); - } - -} 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 c193f5c64..8e12a60c7 100644 --- a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt @@ -27,7 +27,6 @@ import content.global.ame.RandomEvents import content.region.misthalin.draynor.quest.anma.AnmaCutscene import core.game.ge.GrandExchange import content.global.handlers.iface.RulesAndInfo -import content.global.skill.farming.FarmingState import content.minigame.fishingtrawler.TrawlerLoot import core.game.system.command.CommandMapping import core.game.system.command.Privilege @@ -37,6 +36,7 @@ import core.tools.colorize import java.awt.HeadlessException import java.awt.Toolkit import java.awt.datatransfer.StringSelection +import content.global.skill.farming.timers.* @Initializable class MiscCommandSet : CommandSet(Privilege.ADMIN){ @@ -534,19 +534,16 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ } define("grow", Privilege.ADMIN, "", "Grows all planted crops by 1 stage."){ player, _ -> - val state: FarmingState = player.states.get("farming") as FarmingState? ?: return@define + val state = getOrStartTimer (player)!! for(patch in state.getPatches()){ - patch.nextGrowth = System.currentTimeMillis() + patch.nextGrowth = System.currentTimeMillis() - 1 } + + state.run (player) } define("finishbins", Privilege.ADMIN, "", "Finishes any in-progress compost bins."){ player, _ -> - val state: FarmingState = player.states.get("farming") as FarmingState? ?: return@define - - for(bin in state.getBins()){ - bin.finishedTime = System.currentTimeMillis() - } } define("testlady", Privilege.ADMIN){ player, _ -> diff --git a/Server/src/main/core/game/system/command/sets/StatAttributeKeys.kt b/Server/src/main/core/game/system/command/sets/StatAttributeKeys.kt index 1189dbcfb..f39d94ee6 100644 --- a/Server/src/main/core/game/system/command/sets/StatAttributeKeys.kt +++ b/Server/src/main/core/game/system/command/sets/StatAttributeKeys.kt @@ -7,6 +7,8 @@ const val STATS_LOGS = "logs_chopped" const val STATS_FISH = "fish_caught" const val STATS_ROCKS = "rocks_mined" const val STATS_RC = "essence_crafted" +const val STATS_PK_KILLS = "player_kills" +const val STATS_PK_DEATHS = "player_deaths" const val STATS_ALKHARID_GATE = "alkharid_gate" const val FISHING_TRAWLER_GAMES_WON = "fishing-trawler-games" const val FISHING_TRAWLER_LEAKS_PATCHED = "fishing-trawler-leaks-patched" diff --git a/Server/src/main/core/game/system/timer/PersistTimer.kt b/Server/src/main/core/game/system/timer/PersistTimer.kt new file mode 100644 index 000000000..784ca04fb --- /dev/null +++ b/Server/src/main/core/game/system/timer/PersistTimer.kt @@ -0,0 +1,23 @@ +package core.game.system.timer + +import core.api.* +import org.json.simple.* +import core.game.node.entity.Entity +import kotlin.reflect.full.createInstance + +/** + * A timer implementation with support for saving and loading arbitrary data. See `RSTimer` for more info on timers themselves. +**/ +abstract class PersistTimer (runInterval: Int, identifier: String, isSoft: Boolean = false, isAuto: Boolean = false) : RSTimer (runInterval, identifier, isSoft, isAuto) { + open fun save (root: JSONObject, entity: Entity) { + root["ticksLeft"] = (nextExecution - getWorldTicks()).toString() + } + + open fun parse (root: JSONObject, entity: Entity) { + runInterval = root["ticksLeft"].toString().toInt() + } + + override fun retrieveInstance() : RSTimer { + return this::class.createInstance() + } +} diff --git a/Server/src/main/core/game/system/timer/RSTimer.kt b/Server/src/main/core/game/system/timer/RSTimer.kt new file mode 100644 index 000000000..e1fb6b4c7 --- /dev/null +++ b/Server/src/main/core/game/system/timer/RSTimer.kt @@ -0,0 +1,44 @@ +package core.game.system.timer + +import core.game.node.entity.Entity +import kotlin.reflect.full.createInstance + +/** + * Class for the timer feature of the engine. If you have some task which should repeat periodically, such as applying poison damage, etc, use a timer. + * If the `isAuto` value of a timer is set to `true`, then the timer is automatically added to an entity on creation and started. This is separate from the + * default PersistTimer behavior, which automatically starts the timer only if there's saved data for that timer present. In truth, there's very few + * timers that should have isAuto true. +**/ +abstract class RSTimer (var runInterval: Int, val identifier: String = "generictimer", val isSoft: Boolean = false, val isAuto: Boolean = false) { + /** + * Executed every time the run interval of the timer elapses. + * Execution will be delayed if this timer has `isSoft` set to false (which 99% of timers should) if the entity has a modal open or is otherwise stalled. + * @return whether the timer should execute again. If false, timer will be unregistered from the entity and stop executing. If true, timer will be scheduled to repeat once the runInterval elapses again. + **/ + abstract fun run (entity: Entity) : Boolean + + /** + * Called by core code to determine the amount of time between timer scheduling and the initial (first) run. + * Returns the runInterval by default, but cases such as Farming require an override to sync with realtime clocks. + **/ + open fun getInitialRunDelay() : Int { return runInterval } + + /** + * Called by core code when the timer is first registered. Called after parse on PersistTimers. + **/ + open fun onRegister (entity: Entity) {} + + var lastExecution: Int = 0 + var nextExecution: Int = 0 + + open fun retrieveInstance() : RSTimer { + return this::class.createInstance() + } + + /** + * This is called only when getTimer is called by further up code with arguments, otherwise retrieveInstance() is called if no arguments are passed. + **/ + open fun getTimer (vararg args: Any) : RSTimer { + return retrieveInstance() + } +} diff --git a/Server/src/main/core/game/system/timer/TimerManager.kt b/Server/src/main/core/game/system/timer/TimerManager.kt new file mode 100644 index 000000000..e3196a431 --- /dev/null +++ b/Server/src/main/core/game/system/timer/TimerManager.kt @@ -0,0 +1,124 @@ +package core.game.system.timer + +import core.api.* +import core.tools.* +import java.util.ArrayList +import org.json.simple.JSONObject +import core.game.node.entity.Entity +import core.game.node.entity.player.Player + +class TimerManager (val entity: Entity) { + val activeTimers = ArrayList() + val newTimers = ArrayList() + val toRemoveTimers = ArrayList() + + fun registerTimer (timer: RSTimer) { + timer.onRegister(entity) + newTimers.add (timer) + } + + fun processTimers () { + activeTimers.removeAll(toRemoveTimers) + newTimers.removeAll(toRemoveTimers) + toRemoveTimers.clear() + + val canRunNormalTimers = (entity !is Player) || !(entity.asPlayer().hasModalOpen() || entity.scripts.delay > getWorldTicks()) + for (timer in activeTimers) { + if (timer.nextExecution > getWorldTicks()) continue + if (!canRunNormalTimers && !timer.isSoft) continue + + if (timer.run(entity)) { + timer.nextExecution = getWorldTicks() + timer.runInterval + } else { + timer.nextExecution = Int.MAX_VALUE + toRemoveTimers.add(timer) + } + } + + for (timer in newTimers) { + activeTimers.add(timer) + timer.nextExecution = timer.getInitialRunDelay() + getWorldTicks() + } + + newTimers.clear() + } + + fun clearTimers () { + activeTimers.clear() + newTimers.clear() + toRemoveTimers.clear() + } + + fun saveTimers (root: JSONObject) { + for (timer in activeTimers) { + if (timer !is PersistTimer) continue + val obj = JSONObject() + timer.save(obj, entity) + root [timer.identifier] = obj + } + } + + fun parseTimers (root: JSONObject) { + for ((identifier, dataObj) in root) { + val data = dataObj as JSONObject + val timer = TimerRegistry.getTimerInstance (identifier.toString()) as? PersistTimer + + if (timer == null) { + log (this::class.java, Log.ERR, "Tried to load data for persistent timer identified by $identifier, but no such timer seems to exist.") + continue + } + + timer.parse(data, entity) + registerTimer(entity, timer) + } + } + + inline fun removeTimer () { + for (timer in activeTimers) + if (timer is T) + toRemoveTimers.add(timer) + for (timer in newTimers) + if (timer is T) + toRemoveTimers.add(timer) + } + + inline fun getTimer () : T? { + var t: T? = null + for (timer in activeTimers) + if (timer is T) + t = timer + for (timer in newTimers) + if (timer is T) + t = timer + if (t == null) return null + if (toRemoveTimers.contains(t)) + return null + return t + } + + fun getTimer (identifier: String): RSTimer? { + var t: RSTimer? = null + for (timer in activeTimers) + if (timer.identifier == identifier) + t = timer + for (timer in newTimers) + if (timer.identifier == identifier) + t = timer + if (t == null) return null + if (toRemoveTimers.contains(t)) return null + return t + } + + fun removeTimer (identifier: String) { + for (timer in activeTimers) + if (timer.identifier == identifier) + toRemoveTimers.add(timer) + for (timer in newTimers) + if (timer.identifier == identifier) + toRemoveTimers.add(timer) + } + + fun removeTimer (timer: RSTimer) { + toRemoveTimers.add(timer) + } +} diff --git a/Server/src/main/core/game/system/timer/TimerRegistry.kt b/Server/src/main/core/game/system/timer/TimerRegistry.kt new file mode 100644 index 000000000..d687474e4 --- /dev/null +++ b/Server/src/main/core/game/system/timer/TimerRegistry.kt @@ -0,0 +1,50 @@ +package core.game.system.timer + +import java.util.* +import core.api.* +import core.tools.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player + +object TimerRegistry { + val timerMap = HashMap() + val autoTimers = ArrayList() + + fun registerTimer (timer: RSTimer) { + log (this::class.java, Log.WARN, "Registering timer ${timer::class.java.simpleName}") + if (timerMap.containsKey(timer.identifier.lowercase())) { + log (this::class.java, Log.ERR, "Timer identifier ${timer.identifier} already in use by ${timerMap[timer.identifier.lowercase()]!!::class.java.simpleName}! Not loading ${timer::class.java.simpleName}!") + return + } + timerMap[timer.identifier.lowercase()] = timer + if (timer.isAuto) autoTimers.add(timer) + } + + fun getTimerInstance (identifier: String, vararg args: Any) : RSTimer? { + var t = timerMap[identifier.lowercase()] + if (args.size > 0) + return t?.getTimer(*args) + else + return t?.retrieveInstance() + } + + @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()) + } + } + + inline fun getTimerInstance (vararg args: Any) : T? { + for ((_, inst) in timerMap) + if (inst is T) { + if (args.size > 0) + return inst.getTimer(*args) as? T + else + return inst.retrieveInstance() as? T + } + return null + } +} diff --git a/Server/src/main/core/game/system/timer/impl/Disease.kt b/Server/src/main/core/game/system/timer/impl/Disease.kt new file mode 100644 index 000000000..8139ee36e --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/Disease.kt @@ -0,0 +1,46 @@ +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.repository.Repository +import core.game.node.entity.combat.ImpactHandler +import core.tools.RandomFunction +import org.json.simple.* + +class Disease : PersistTimer (30, "disease") { + var hitsLeft = 25 + + override fun save (root: JSONObject, entity: Entity) { + root["hitsLeft"] = hitsLeft.toString() + } + + override fun parse (root: JSONObject, entity: Entity) { + hitsLeft = root["hitsLeft"].toString().toInt() + } + + override fun onRegister (entity: Entity) { + if (hasTimerActive(entity)) + removeTimer(entity, this) + else if (entity is Player) + sendMessage(entity, "You have become diseased.") + } + + override fun run (entity: Entity) : Boolean { + var damage = RandomFunction.random(1, 5) + entity.impactHandler.manualHit(entity,damage,ImpactHandler.HitsplatType.DISEASE) + var skillId = RandomFunction.random(24) + if(skillId == 3) skillId-- + entity.skills.updateLevel(skillId,-damage,0) + if (--hitsLeft == 0 && entity is Player) + sendMessage(entity, "The disease has wore off.") + return hitsLeft > 0 + } + + override fun getTimer (vararg args: Any) : RSTimer { + val inst = Disease() + inst.hitsLeft = args.getOrNull(0) as? Int ?: 25 + return inst + } +} diff --git a/Server/src/main/core/game/system/timer/impl/Frozen.kt b/Server/src/main/core/game/system/timer/impl/Frozen.kt new file mode 100644 index 000000000..d028259d7 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/Frozen.kt @@ -0,0 +1,48 @@ +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.repository.Repository +import org.json.simple.* + +class Frozen : PersistTimer (1, "frozen") { + var shouldApplyImmunity = false + + override fun save (root: JSONObject, entity: Entity) { + root["ticksLeft"] = (nextExecution - getWorldTicks()).toString() + root["applyImmunity"] = shouldApplyImmunity + } + + override fun parse (root: JSONObject, entity: Entity) { + runInterval = root["ticksLeft"].toString().toInt() + shouldApplyImmunity = root["applyImmunity"] as? Boolean ?: false + } + + override fun onRegister (entity: Entity) { + if (hasTimerActive(entity)) { + removeTimer(entity, this) + return + } + if (hasTimerActive(entity)) { + removeTimer(entity, this) + return + } + } + + override fun run (entity: Entity) : Boolean { + if (shouldApplyImmunity) { + registerTimer (entity, spawnTimer(7)) + } else (entity as? Player)?.debug ("Can't apply immunity") + return false + } + + override fun getTimer (vararg args: Any) : RSTimer { + val inst = Frozen() + println(args) + inst.runInterval = args.getOrNull(0) as? Int ?: 10 + inst.shouldApplyImmunity = args.getOrNull(1) as? Boolean ?: false + return inst + } +} diff --git a/Server/src/main/core/game/system/timer/impl/FrozenImmunity.kt b/Server/src/main/core/game/system/timer/impl/FrozenImmunity.kt new file mode 100644 index 000000000..4809c5d32 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/FrozenImmunity.kt @@ -0,0 +1,38 @@ +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.world.repository.Repository +import org.json.simple.* + +class FrozenImmunity : PersistTimer (1, "frozen:immunity") { + var ticksRemaining = 0 + + override fun save (root: JSONObject, entity: Entity) { + root["ticksLeft"] = (nextExecution - getWorldTicks()).toString() + } + + override fun parse (root: JSONObject, entity: Entity) { + runInterval = root["ticksRemaining"].toString().toInt() + } + + override fun onRegister (entity: Entity) { + if (hasTimerActive(entity)) { + removeTimer(entity) + } + (entity as? Player)?.debug("Applied frozen immunity for $runInterval ticks.") + } + + override fun run (entity: Entity) : Boolean { + (entity as? Player)?.debug("Removed frozen immunity") + return false + } + + override fun getTimer (vararg args: Any) : RSTimer { + val inst = FrozenImmunity() + inst.runInterval = args.getOrNull(0) as? Int ?: 7 + return inst + } +} diff --git a/Server/src/main/core/game/system/timer/impl/HealOverTime.kt b/Server/src/main/core/game/system/timer/impl/HealOverTime.kt new file mode 100644 index 000000000..8291857e7 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/HealOverTime.kt @@ -0,0 +1,47 @@ +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import org.json.simple.* +import kotlin.math.min + +class HealOverTime : PersistTimer (1, "healovertime") { + var healRemaining = 0 + var healPerTick = 0 + + override fun run (entity: Entity) : Boolean { + var amt = min (healRemaining, healPerTick) + healRemaining -= amt + entity.skills.heal(amt) + return healRemaining > 0 + } + + override fun save (root: JSONObject, entity: Entity) { + super.save (root, entity) + root["healRemaining"] = healRemaining.toString() + root["healPerTick"] = healPerTick.toString() + } + + override fun parse (root: JSONObject, entity: Entity) { + super.parse (root, entity) + healRemaining = root["healRemaining"].toString().toInt() + healPerTick = root["healPerTick"].toString().toInt() + } + + override fun onRegister (entity: Entity) { + if (hasTimerActive(entity)) + removeTimer(entity, this) + if (hasTimerActive(entity)) + removeTimer(entity, this) + } + + override fun getTimer (vararg args: Any) : RSTimer { + val t = HealOverTime() + t.runInterval = args.getOrNull(0) as? Int ?: 100 + t.healRemaining = args.getOrNull(1) as? Int ?: 10 + t.healPerTick = args.getOrNull(2) as? Int ?: 2 + return t + } +} diff --git a/Server/src/main/core/game/system/timer/impl/Miasmic.kt b/Server/src/main/core/game/system/timer/impl/Miasmic.kt new file mode 100644 index 000000000..e9fff66b3 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/Miasmic.kt @@ -0,0 +1,27 @@ +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import org.json.simple.* + +class Miasmic : PersistTimer (1, "miasmic") { + override fun run (entity: Entity) : Boolean { + registerTimer (entity, spawnTimer(entity, 7)) + return false + } + + override fun onRegister (entity: Entity) { + if (hasTimerActive(entity)) + removeTimer(entity, this) + if (hasTimerActive(entity)) + removeTimer(entity, this) + } + + override fun getTimer (vararg args: Any) : RSTimer { + val t = Miasmic() + t.runInterval = args.getOrNull(0) as? Int ?: 100 + return t + } +} diff --git a/Server/src/main/core/game/system/timer/impl/MiasmicImmunity.kt b/Server/src/main/core/game/system/timer/impl/MiasmicImmunity.kt new file mode 100644 index 000000000..52a87f119 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/MiasmicImmunity.kt @@ -0,0 +1,24 @@ +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import org.json.simple.* + +class MiasmicImmunity : PersistTimer (1, "miasmic:immunity") { + override fun run (entity: Entity) : Boolean { + return false + } + + override fun onRegister (entity: Entity) { + if (hasTimerActive(entity)) + removeTimer(entity) + } + + override fun getTimer (vararg args: Any) : RSTimer { + val t = MiasmicImmunity() + t.runInterval = args.getOrNull(0) as? Int ?: 100 + return t + } +} diff --git a/Server/src/main/core/game/system/timer/impl/Poison.kt b/Server/src/main/core/game/system/timer/impl/Poison.kt new file mode 100644 index 000000000..73384a411 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/Poison.kt @@ -0,0 +1,79 @@ +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.node.entity.combat.ImpactHandler +import core.game.world.repository.Repository +import org.json.simple.* + +/** + * A timer that replicates the behavior of poison mechanics. Runs every 30 ticks. + * If you wish to construct an instance of this Timer, consider using the ContentAPI function `applyPoison` instead. + * If that doesn't suit your needs, then use `Poison.getTimer (source, severity)` and then `registerTimer(entity, timer)` + * Poison mechanics are driven by the `severity` value, which is not a 1:1 representation of damage. Instead the formula `floor((severity + 4) / 5)` is used. + * Every time the damage is applied, the severity decreases by 1. Poison ends when severity reaches 0. + * Example: 30 Severity. Deals 6 damage 5 times, then 5 damage 5 times, and so on. +**/ +class Poison : PersistTimer (30, "poison") { + lateinit var damageSource: Entity + + var severity = 0 + set (value) { + if (value != field - 1 && value % 10 == 8) {//This was Arios's incorrect attempt at replicating severity, convert it to correct values. + (damageSource as? Player)?.debug ("[PoisonTimer] Warning: Converting suspect Arios severity into true severity. If numbers look wrong, this could be why.") + field = (value / 10) * 5 + (damageSource as? Player)?.debug ("[PoisonTimer] Warning: New Severity: $field.") + } else field = value + } + + override fun save (root: JSONObject, entity: Entity) { + root["source-uid"] = (damageSource as? Player)?.details?.uid ?: -1 + root["severity"] = severity.toString() + } + + override fun parse (root: JSONObject, entity: Entity) { + val uid = root["source-uid"].toString().toInt() + severity = root["severity"].toString().toInt() + damageSource = Repository.getPlayerByUid (uid) ?: entity + } + + override fun onRegister (entity: Entity) { + if (hasTimerActive(entity)) { + removeTimer(entity) + return + } + if (entity is Player) { + sendMessage(entity, "You have been poisoned.") + entity.debug ("[Poison] -> Received for $severity severity.") + } + if (damageSource is Player) + (damageSource as? Player)?.debug ("[Poison] -> Applied for $severity severity.") + } + + override fun run (entity: Entity) : Boolean { + entity.impactHandler.manualHit ( + damageSource, + getDamageFromSeverity (severity--), + ImpactHandler.HitsplatType.POISON + ) + + if (severity == 0 && entity is Player) + sendMessage(entity, "The poison has wore off.") + return severity > 0 + } + + override fun getTimer (vararg args: Any) : RSTimer { + val timer = Poison() + for (arg in args) + println(arg) + timer.damageSource = args[0] as? Entity ?: return timer + timer.severity = args[1] as? Int ?: return timer + return timer + } + + private fun getDamageFromSeverity (severity: Int) : Int { + return (severity + 4) / 5 + } +} diff --git a/Server/src/main/core/game/system/timer/impl/PoisonImmunity.kt b/Server/src/main/core/game/system/timer/impl/PoisonImmunity.kt new file mode 100644 index 000000000..c3810def2 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/PoisonImmunity.kt @@ -0,0 +1,53 @@ +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.tools.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.node.entity.combat.ImpactHandler +import core.game.world.repository.Repository +import core.game.node.entity.player.link.audio.Audio +import org.json.simple.* + +/** + * A timer that replicates the behavior of poison immunity mechanics. Runs every tick. + * Will notify the player of various levels of remaining poison immunity, and then remove itself once it has run out. + * This timer is a "soft" timer, meaning it will tick down even while other timers would normally stall (e.g. during entity delays or when the entity has a modal open.) +**/ +class PoisonImmunity : PersistTimer (1, "poison:immunity", isSoft = true) { + var ticksRemaining = 0 + + override fun save (root: JSONObject, entity: Entity) { + root["ticksRemaining"] = ticksRemaining.toString() + } + + override fun parse (root: JSONObject, entity: Entity) { + ticksRemaining = root["ticksRemaining"].toString().toInt() + } + + override fun onRegister (entity: Entity) { + removeTimer(entity) + } + + override fun run (entity: Entity) : Boolean { + ticksRemaining-- + + if (entity is Player && ticksRemaining == secondsToTicks(30)) { + sendMessage(entity, colorize("%RYou have 30 seconds remaining on your poison immunity.")) + playAudio(entity, Audio(3120)) + } + else if (entity is Player && ticksRemaining == 0) { + sendMessage(entity, colorize("%RYour poison immunity has expired.")) + playAudio(entity, Audio(2607)) + } + + return ticksRemaining > 0 + } + + override fun getTimer (vararg args: Any) : RSTimer { + val t = PoisonImmunity() + t.ticksRemaining = args.getOrNull(0) as? Int ?: 100 + return t + } +} diff --git a/Server/src/main/core/game/system/timer/impl/SkillRestore.kt b/Server/src/main/core/game/system/timer/impl/SkillRestore.kt new file mode 100644 index 000000000..092ae905b --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/SkillRestore.kt @@ -0,0 +1,101 @@ +package core.game.system.timer.impl + +import core.api.* +import core.api.Event +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import core.game.node.entity.player.link.prayer.PrayerType +import core.game.node.entity.skill.Skills +import core.game.system.timer.* +import core.game.event.* + +import org.rs09.consts.Items; + +class SkillRestore : RSTimer (1, "skillrestore", isAuto = true, isSoft = true) { + val ticksSinceLastRestore = IntArray (25) + val restoreTicks = IntArray (25) { 100 } + + override fun run (entity: Entity) : Boolean { + var skills = entity.skills + + for (i in 0 until 24) { + if (i == Skills.PRAYER) continue + if (ticksSinceLastRestore[i]++ >= restoreTicks[i]) { + if (i == Skills.HITPOINTS) { + skills.heal (getHealAmount(entity)) + } else { + val max = getStatLevel (entity, i) + val current = getDynLevel (entity, i) + + if (current != max) + skills.updateLevel (i, if (current < max) 1 else -1, max); + } + ticksSinceLastRestore[i] = 0 + } + } + + if (entity is Player && ticksSinceLastRestore[24]++ >= 50) { + entity.settings.setSpecialEnergy (kotlin.math.min (100, entity.settings.specialEnergy + 10)) + ticksSinceLastRestore[24] = 0 + } + + return true + } + + override fun onRegister (entity: Entity) { + entity.hook (Event.PrayerActivated, PrayerActivatedHook) + entity.hook (Event.PrayerDeactivated, PrayerDeactivatedHook) + (entity as? Player)?.debug("Registered skill restoration timer.") + } + + private fun getHealAmount (entity: Entity) : Int { + if (entity !is Player) return 1 + + val gloves = getItemFromEquipment (entity, EquipmentSlot.HANDS) + if (gloves == null || gloves.id != Items.REGEN_BRACELET_11133) + return 1 + else return 2 + } + + object PrayerActivatedHook : EventHook { + override fun process (entity: Entity, event: PrayerActivatedEvent) { + val restore = getOrStartTimer (entity) + + when (event.type) { + PrayerType.RAPID_HEAL -> { + restore.restoreTicks [Skills.HITPOINTS] -= 50 + restore.ticksSinceLastRestore [Skills.HITPOINTS] = 0 + } + PrayerType.RAPID_RESTORE -> { + for (i in 0 until 24) { + if (i == Skills.HITPOINTS || i == Skills.PRAYER || i == Skills.SUMMONING) continue + restore.restoreTicks [i] -= 50 + restore.ticksSinceLastRestore [i] = 0 + } + } + else -> {} + } + } + } + + object PrayerDeactivatedHook : EventHook { + override fun process (entity: Entity, event: PrayerDeactivatedEvent) { + val restore = getOrStartTimer (entity) + + when (event.type) { + PrayerType.RAPID_HEAL -> { + restore.restoreTicks [Skills.HITPOINTS] += 50 + restore.ticksSinceLastRestore [Skills.HITPOINTS] = 0 + } + PrayerType.RAPID_RESTORE -> { + for (i in 0 until 24) { + if (i == Skills.HITPOINTS || i == Skills.PRAYER || i == Skills.SUMMONING) continue + restore.restoreTicks [i] += 50 + restore.ticksSinceLastRestore [i] = 0 + } + } + else -> {} + } + } + } +} diff --git a/Server/src/main/core/game/system/timer/impl/Skulled.kt b/Server/src/main/core/game/system/timer/impl/Skulled.kt new file mode 100644 index 000000000..12a51a761 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/Skulled.kt @@ -0,0 +1,27 @@ +package core.game.system.timer.impl + +import core.api.* +import core.game.system.timer.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import org.json.simple.* + +class Skulled : PersistTimer (1, "skulled") { + override fun onRegister (entity: Entity) { + if (entity !is Player) return + entity.skullManager.setSkullIcon(0) + entity.skullManager.setSkulled(true) + } + + override fun run (entity: Entity) : Boolean { + if (entity !is Player) return false + entity.skullManager.reset() + return false + } + + override fun getTimer (vararg args: Any) : RSTimer { + val t = Skulled() + t.runInterval = args.getOrNull(0) as? Int ?: 500 + return t + } +} diff --git a/Server/src/main/core/game/system/timer/impl/Teleblock.kt b/Server/src/main/core/game/system/timer/impl/Teleblock.kt new file mode 100644 index 000000000..9caa5c4b4 --- /dev/null +++ b/Server/src/main/core/game/system/timer/impl/Teleblock.kt @@ -0,0 +1,24 @@ +package core.game.system.timer.impl + +import core.game.system.timer.* +import core.api.* +import core.game.node.entity.Entity +import core.game.node.entity.player.Player +import org.json.simple.* + +class Teleblock : PersistTimer (1, "teleblock") { + override fun run (entity: Entity) : Boolean { + return false + } + + override fun onRegister (entity: Entity) { + if (entity !is Player) return + sendMessage (entity, "You have been teleblocked.") + } + + override fun getTimer (vararg args: Any) : RSTimer { + val t = Teleblock() + t.runInterval = args.getOrNull(0) as? Int ?: 100 + return t + } +} diff --git a/Server/src/main/core/game/world/GameWorld.kt b/Server/src/main/core/game/world/GameWorld.kt index ba47dc361..2c0b55e2c 100644 --- a/Server/src/main/core/game/world/GameWorld.kt +++ b/Server/src/main/core/game/world/GameWorld.kt @@ -169,6 +169,7 @@ object GameWorld { ConfigParser().prePlugin() ClassScanner.scanClasspath() ClassScanner.loadPureInterfaces() + ClassScanner.loadTimers() val s = worldPersists.filterIsInstance().first() s.parse() worldPersists.filter { it !is ServerStore }.forEach { it.parse() } @@ -216,4 +217,4 @@ object GameWorld { generateLocation() } else random_location } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/world/repository/Repository.kt b/Server/src/main/core/game/world/repository/Repository.kt index 08bf2b7c6..0d2e73fbc 100644 --- a/Server/src/main/core/game/world/repository/Repository.kt +++ b/Server/src/main/core/game/world/repository/Repository.kt @@ -191,6 +191,11 @@ object Repository { } else playerNames[name.toLowerCase().replace(" ".toRegex(), "_")] } + @JvmStatic + fun getPlayerByUid(uid: Int) : Player? { + return uid_map[uid] + } + /** * Gets the renderableNpcs. * @return The renderableNpcs. @@ -198,4 +203,4 @@ object Repository { @JvmStatic val renderableNpcs: List get() = RENDERABLE_NPCS -} \ No newline at end of file +} diff --git a/Server/src/main/core/plugin/ClassScanner.kt b/Server/src/main/core/plugin/ClassScanner.kt index be189598c..857e46f7d 100644 --- a/Server/src/main/core/plugin/ClassScanner.kt +++ b/Server/src/main/core/plugin/ClassScanner.kt @@ -13,6 +13,7 @@ import core.game.world.map.zone.ZoneBuilder import io.github.classgraph.ClassGraph import io.github.classgraph.ClassInfo import io.github.classgraph.ScanResult +import core.game.system.timer.* import core.game.bots.PlayerScripts import core.game.interaction.InteractionListener import core.game.interaction.InterfaceListener @@ -73,6 +74,18 @@ object ClassScanner { } } + fun loadTimers () { + scanResults.getSubclasses ("core.game.system.timer.RSTimer").filter { !it.isAbstract }.forEach { + try { + val clazz = it.loadClass().newInstance() as RSTimer + TimerRegistry.registerTimer (clazz) + } catch (e: Exception) { + log(this::class.java, Log.ERR, "Error registering timer instance: ${it.simpleName}") + e.printStackTrace() + } + } + } + private fun loadContentInterfacesFrom(scanResults: ScanResult) { scanResults.getClassesImplementing("core.api.ContentInterface").filter { !it.isAbstract }.forEach { try {