diff --git a/Server/src/main/java/core/game/node/entity/Entity.java b/Server/src/main/java/core/game/node/entity/Entity.java index 5df59c64a..428c3e9ad 100644 --- a/Server/src/main/java/core/game/node/entity/Entity.java +++ b/Server/src/main/java/core/game/node/entity/Entity.java @@ -2,6 +2,7 @@ package core.game.node.entity; import api.events.Event; import api.events.EventHook; +import api.events.TickEvent; import core.game.content.holiday.HolidayEvent; import core.game.interaction.DestinationFlag; import core.game.node.Node; @@ -216,6 +217,7 @@ public abstract class Entity extends Node { * This methods gets called before the {@link #update()} method. */ public void tick() { + dispatch(new TickEvent(GameWorld.getTicks())); skills.pulse(); walkingQueue.update(); updateMasks.prepare(this); diff --git a/Server/src/main/java/core/game/node/entity/impl/WalkingQueue.java b/Server/src/main/java/core/game/node/entity/impl/WalkingQueue.java index 49fd4d737..e6b0d6fda 100644 --- a/Server/src/main/java/core/game/node/entity/impl/WalkingQueue.java +++ b/Server/src/main/java/core/game/node/entity/impl/WalkingQueue.java @@ -70,6 +70,9 @@ public final class WalkingQueue { boolean isPlayer = entity instanceof Player; this.walkDir = -1; this.runDir = -1; + if(entity.getLocation() == null) { + return; + } if (updateTeleport()) { return; } diff --git a/Server/src/main/kotlin/api/events/Events.kt b/Server/src/main/kotlin/api/events/Events.kt index faf15dedf..653bba333 100644 --- a/Server/src/main/kotlin/api/events/Events.kt +++ b/Server/src/main/kotlin/api/events/Events.kt @@ -13,4 +13,4 @@ data class InteractionEvent(val target: Node, val option: String) : Event data class ButtonClickedEvent(val iface: Int, val buttonId: Int) : Event data class UsedWithEvent(val used: Int, val with: Int) : Event data class SelfDeath(val killer: Entity) : Event -data class TickEvent(val source: Entity) : Event \ No newline at end of file +data class TickEvent(val worldTicks: Int) : Event \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/ame/RandomEventManager.kt b/Server/src/main/kotlin/rs09/game/content/ame/RandomEventManager.kt index 55d90efe2..15444a7fc 100644 --- a/Server/src/main/kotlin/rs09/game/content/ame/RandomEventManager.kt +++ b/Server/src/main/kotlin/rs09/game/content/ame/RandomEventManager.kt @@ -14,9 +14,6 @@ import rs09.game.system.SystemLogger import rs09.game.world.GameWorld import kotlin.random.Random -private const val AVG_DELAY_TICKS = 6000 // 60 minutes -private const val MIN_DELAY_TICKS = AVG_DELAY_TICKS / 2 -private const val MAX_DELAY_TICKS = MIN_DELAY_TICKS + AVG_DELAY_TICKS // window of 60 min centered on 60 min (30 to 90 min) class RandomEventManager(val player: Player? = null) : LoginListener, EventHook { var event: RandomEventNPC? = null var enabled: Boolean = false @@ -47,7 +44,7 @@ class RandomEventManager(val player: Player? = null) : LoginListener, EventHook< return } if (getAttribute(player, "re-npc", null) != null) return - val currentAction = player.pulseManager.current.toString() + val currentAction = player.pulseManager.current?.toString() ?: "None" val ame: RandomEvents = if(currentAction.contains("WoodcuttingSkillPulse") && Random.nextBoolean()){ RandomEvents.TREE_SPIRIT } else if(currentAction.contains("FishingPulse") && Random.nextBoolean()){ @@ -62,6 +59,7 @@ class RandomEventManager(val player: Player? = null) : LoginListener, EventHook< event = ame.npc.create(player,ame.loot,ame.type) if (event!!.spawnLocation == null) { nextSpawn = GameWorld.ticks + 3000 + SystemLogger.logWarn("Tried to spawn random event for ${player.username} but spawn location was null!") return } event!!.init() @@ -74,6 +72,10 @@ class RandomEventManager(val player: Player? = null) : LoginListener, EventHook< } companion object { + const val AVG_DELAY_TICKS = 6000 // 60 minutes + const val MIN_DELAY_TICKS = AVG_DELAY_TICKS / 2 + const val MAX_DELAY_TICKS = MIN_DELAY_TICKS + AVG_DELAY_TICKS // window of 60 min centered on 60 min (30 to 90 min) + @JvmStatic fun getInstance(player: Player): RandomEventManager? { return getAttribute(player, "random-manager", null) diff --git a/Server/src/main/kotlin/rs09/game/content/ame/events/evilchicken/EvilChickenNPC.kt b/Server/src/main/kotlin/rs09/game/content/ame/events/evilchicken/EvilChickenNPC.kt index 50437de07..fe9d31d7b 100644 --- a/Server/src/main/kotlin/rs09/game/content/ame/events/evilchicken/EvilChickenNPC.kt +++ b/Server/src/main/kotlin/rs09/game/content/ame/events/evilchicken/EvilChickenNPC.kt @@ -9,6 +9,7 @@ import core.tools.RandomFunction import org.rs09.consts.Items import rs09.game.content.ame.RandomEventNPC import rs09.game.content.global.WeightBasedTable +import java.lang.Integer.max val ids = 2463..2468 @@ -18,7 +19,7 @@ class EvilChickenNPC(override var loot: WeightBasedTable? = null) : RandomEventN override fun init() { super.init() - val index = (player.properties.combatLevel / 20) - 1 + val index = max(0, (player.properties.combatLevel / 20) - 1) val id = ids.toList()[index] this.transform(id) this.attack(player) diff --git a/Server/src/main/kotlin/rs09/game/world/update/UpdateSequence.kt b/Server/src/main/kotlin/rs09/game/world/update/UpdateSequence.kt index b4aaf499a..ec49ebb54 100644 --- a/Server/src/main/kotlin/rs09/game/world/update/UpdateSequence.kt +++ b/Server/src/main/kotlin/rs09/game/world/update/UpdateSequence.kt @@ -38,7 +38,7 @@ class UpdateSequence playersList = renderablePlayers npcList = Repository.renderableNpcs lobbyList!!.map{ PacketRepository.send(ClearMinimapFlag::class.java, PlayerContext(it)) } - playersList!!.forEach(Player::tick) + renderablePlayers.forEach(Player::tick) npcList!!.forEach(NPC::tick) } diff --git a/Server/src/test/kotlin/TestUtils.kt b/Server/src/test/kotlin/TestUtils.kt index a949f9364..05f11ebd5 100644 --- a/Server/src/test/kotlin/TestUtils.kt +++ b/Server/src/test/kotlin/TestUtils.kt @@ -11,18 +11,21 @@ import org.rs09.consts.Items import rs09.ServerConstants import rs09.game.content.global.shops.Shop import rs09.game.content.global.shops.ShopItem +import rs09.game.system.SystemLogger import rs09.game.system.config.ConfigParser import rs09.game.system.config.ServerConfigParser import rs09.game.world.GameWorld import rs09.game.world.repository.Repository +import rs09.game.world.update.UpdateSequence import java.nio.ByteBuffer object TestUtils { fun getMockPlayer(name: String, ironman: IronmanMode = IronmanMode.NONE): Player { - val p = Player(PlayerDetails(name)) - p.details.session = MockSession() + val p = MockPlayer(name) p.ironmanManager.mode = ironman Repository.addPlayer(p) + //Update sequence has a separate list of players for some reason... + UpdateSequence.renderablePlayers.add(p) return p } @@ -53,12 +56,23 @@ object TestUtils { } fun advanceTicks(amount: Int) { + SystemLogger.logInfo("Advancing ticks by $amount.") for(i in 0 until amount) { GameWorld.majorUpdateWorker.handleTickActions() } } } +class MockPlayer(name: String) : Player(PlayerDetails(name)) { + init { + this.details.session = MockSession() + } + + override fun update() { + //do nothing. This is for rendering stuff. We don't render a mock player. Not until the spaghetti is less spaghetti. + } +} + class MockSession : IoSession(null, null) { val receivedPackets = ArrayList() var disconnected = false diff --git a/Server/src/test/kotlin/content/RandomEventManager.kt b/Server/src/test/kotlin/content/RandomEventManager.kt new file mode 100644 index 000000000..075e40134 --- /dev/null +++ b/Server/src/test/kotlin/content/RandomEventManager.kt @@ -0,0 +1,36 @@ +package content + +import TestUtils +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import rs09.game.world.GameWorld + +class RandomEventManager { + companion object {init { + TestUtils.preTestSetup() + }} + + @Test fun loginShouldEnableManager() { + val p = TestUtils.getMockPlayer("Bill") + rs09.game.content.ame.RandomEventManager().login(p) + val manager = rs09.game.content.ame.RandomEventManager.getInstance(p) + Assertions.assertNotNull(manager) + Assertions.assertEquals(true, manager!!.enabled) + } + + @Test fun loginShouldSetNextSpawn() { + val p = TestUtils.getMockPlayer("Bill") + rs09.game.content.ame.RandomEventManager().login(p) + val manager = rs09.game.content.ame.RandomEventManager.getInstance(p) + Assertions.assertNotNull(manager) + Assertions.assertEquals(true, manager!!.nextSpawn > GameWorld.ticks) + } + + @Test fun shouldSpawnRandomEventWithinMAXTICKSGivenNoRestrictions() { + val p = TestUtils.getMockPlayer("Bill") + p.setAttribute("tutorial:complete", true) //tutorial has to be complete to spawn randoms + rs09.game.content.ame.RandomEventManager().login(p) + TestUtils.advanceTicks(rs09.game.content.ame.RandomEventManager.MAX_DELAY_TICKS + 5) + Assertions.assertNotNull(p.getAttribute("re-npc", null)) + } +} \ No newline at end of file