diff --git a/data/skill/range/ammo_groups.toml b/data/skill/range/ammo_groups.toml index b6761d588d..d653f14d0b 100644 --- a/data/skill/range/ammo_groups.toml +++ b/data/skill/range/ammo_groups.toml @@ -72,6 +72,10 @@ items = [ "rune_arrow", "rune_fire_arrows_lit", "rune_fire_arrows_unlit", + "dragon_arrow", + "dragon_arrow_p", + "dragon_arrow_p+", + "dragon_arrow_p++", "ice_arrows", "broad_arrows", "saradomin_arrows", diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt index 4353a05984..541917bf13 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt @@ -41,7 +41,6 @@ class AccountManager( get() = Tile(Settings["world.home.x", 0], Settings["world.home.y", 0], Settings["world.home.level", 0]) fun create(name: String, passwordHash: String): Player = Player(tile = homeTile, accountName = name, passwordHash = passwordHash).apply { - this["creation"] = System.currentTimeMillis() this["new_player"] = true } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/combat/CombatMovement.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/combat/CombatMovement.kt index 7961124222..7a922d6fd1 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/combat/CombatMovement.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/combat/CombatMovement.kt @@ -16,9 +16,7 @@ import world.gregs.voidps.engine.entity.character.player.chat.cantReach import world.gregs.voidps.engine.entity.item.Item import world.gregs.voidps.engine.get import world.gregs.voidps.engine.map.Overlap -import world.gregs.voidps.type.Direction import world.gregs.voidps.type.Tile -import world.gregs.voidps.type.random import kotlin.math.abs /** @@ -60,7 +58,6 @@ class CombatMovement( if (!attack()) { var skip: Boolean if (Overlap.isUnder(character.tile, character.size, target.tile, target.size)) { - stepOut() skip = true } else { val wasEmpty = character.steps.isEmpty() @@ -81,17 +78,8 @@ class CombatMovement( } } - private fun stepOut() { - clearSteps() - if (target.mode is CombatMovement || target.mode is Interact) { - return - } - val direction = Direction.cardinal.random(random) - if (!canStep(direction.delta.x, direction.delta.y)) { - return - } - character.steps.queueStep(strategy.tile.add(direction)) - } + override fun shouldQueueStepOut(): Boolean = + target.mode !is CombatMovement && target.mode !is Interact private fun attack(): Boolean { val attackRange = attackRange() diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/interact/Interact.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/interact/Interact.kt index 51d4d0bd7a..6ef1d067be 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/interact/Interact.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/interact/Interact.kt @@ -8,10 +8,14 @@ import world.gregs.voidps.engine.entity.character.Character import world.gregs.voidps.engine.entity.character.mode.EmptyMode import world.gregs.voidps.engine.entity.character.mode.move.Movement import world.gregs.voidps.engine.entity.character.mode.move.target.TargetStrategy +import world.gregs.voidps.engine.entity.character.npc.NPC import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.chat.cantReach import world.gregs.voidps.engine.entity.character.player.chat.noInterest +import world.gregs.voidps.engine.map.Overlap import world.gregs.voidps.engine.suspend.resumeSuspension +import world.gregs.voidps.type.Direction +import world.gregs.voidps.type.random /** * Moves a player within interact distance of [target] @@ -86,6 +90,18 @@ open class Interact( return } updateRange = false + val target = target + val npc = character as? NPC + if (npc != null && !npc.def["allowed_under", false] && target is Character && + Overlap.isUnder(npc.tile, npc.size, target.tile, target.size)) { + clearSteps() + val direction = Direction.cardinal.random(random) + if (canStep(direction.delta.x, direction.delta.y)) { + character.steps.queueStep(npc.tile.add(direction)) + } + super.tick() + return + } calculate() val interacted = processInteraction() if (interacted && interactionFinished()) { diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt index f6e66dc622..9fb88c5a86 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/mode/move/Movement.kt @@ -26,6 +26,7 @@ import world.gregs.voidps.type.Delta import world.gregs.voidps.type.Direction import world.gregs.voidps.type.Tile import world.gregs.voidps.type.equals +import world.gregs.voidps.type.random import kotlin.math.sign open class Movement( @@ -53,6 +54,30 @@ open class Movement( needsCalculation = false } + /** + * Clears steps and queues a random cardinal step when an NPC overlaps its character target and isn't permitted to stand there. + */ + protected open fun stepOut(): Boolean { + val strategy = strategy ?: return false + if (strategy.shape != -2) return false + val npc = character as? NPC ?: return false + if (npc.def["allowed_under", false]) return false + if (!Overlap.isUnder(npc.tile, npc.size, npc.size, strategy.tile, strategy.width, strategy.height)) return false + clearSteps() + if (shouldQueueStepOut()) { + val direction = Direction.cardinal.random(random) + if (canStep(direction.delta.x, direction.delta.y)) { + character.steps.queueStep(npc.tile.add(direction)) + } + } + return true + } + + /** + * Whether [stepOut] should queue a random step after clearing, or let normal recalculation handle repositioning. + */ + protected open fun shouldQueueStepOut(): Boolean = true + override fun tick() { val character = character if (character is Player && character.viewport?.loaded == false) { @@ -61,7 +86,9 @@ open class Movement( if (hasDelay() && !canMove() && !character.steps.destination.noCollision) { return } - calculate() + if (!stepOut()) { + calculate() + } if (step(runStep = false) && character.running) { if (character.steps.isNotEmpty()) { step(runStep = true) diff --git a/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountManagerTest.kt b/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountManagerTest.kt index 813b11793a..b356cb7d1b 100644 --- a/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountManagerTest.kt +++ b/engine/src/test/kotlin/world/gregs/voidps/engine/data/AccountManagerTest.kt @@ -85,9 +85,7 @@ class AccountManagerTest : KoinMock() { @Test fun `Create a new player`() { - val start = System.currentTimeMillis() val player = manager.create("name", "hash") - assertTrue(player["creation", 0L] >= start) assertTrue(player["new_player", false]) assertEquals(Tile(1234, 5432), player.tile) } diff --git a/game/src/main/kotlin/content/area/karamja/tzhaar_city/TzhaarFightCave.kt b/game/src/main/kotlin/content/area/karamja/tzhaar_city/TzhaarFightCave.kt index 749cffc664..94c708c687 100644 --- a/game/src/main/kotlin/content/area/karamja/tzhaar_city/TzhaarFightCave.kt +++ b/game/src/main/kotlin/content/area/karamja/tzhaar_city/TzhaarFightCave.kt @@ -123,6 +123,9 @@ class TzhaarFightCave( return@npcDespawn } val wave = killer.wave + if (wave == -1) { + return@npcDespawn + } if (wave == 63 && id == "tztok_jad") { killer.leave(wave, true) } else if (wave < 63) { diff --git a/game/src/test/kotlin/content/entity/combat/CombatMovementTest.kt b/game/src/test/kotlin/content/entity/combat/CombatMovementTest.kt index f7eab10371..a911d14b8b 100644 --- a/game/src/test/kotlin/content/entity/combat/CombatMovementTest.kt +++ b/game/src/test/kotlin/content/entity/combat/CombatMovementTest.kt @@ -129,6 +129,16 @@ internal class CombatMovementTest : WorldTest() { assertTrue(npc.mode is CombatMovement) } + @Test + fun `Npc spawned under player steps out to attack`() { + val player = createPlayer(emptyTile) + val npc = createNPC("guard_falador", emptyTile) + npc.interactPlayer(player, "Attack") + tick(2) + assertTrue(npc.tile != emptyTile) + assertTrue(npc.mode is CombatMovement) + } + companion object { private const val MAX_EXP = 14000000.0 }