diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsCatapultInterface.kt b/Server/src/main/content/minigame/castlewars/CastleWarsCatapultInterface.kt index 448e3eec3..c4db59533 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsCatapultInterface.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsCatapultInterface.kt @@ -53,6 +53,11 @@ class CastleWarsCatapultInterface : InterfaceListener { } private fun handleFire(player: Player, coord: Coordinate, team: CastleWarsTeam): Boolean { + if (CatapultFiringHandler.hasShotInFlight(player)) { + CatapultFiringHandler.sendShotInFlightMessage(player) + return closeAndReturn(player, true) + } + if (!hasRocks(player)) { sendMessage(player, "You don't have any rocks to fire!") return true diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt index 404b59652..2d5ede95d 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt @@ -228,8 +228,8 @@ class CastleWarsListeners : InteractionListener { } else { val formattedItemName = (ItemDefinition.forId(rewardItem).name.lowercase() + "s.").replace("pes.", "pe.").replace( - "bronze ", "" - ) + "bronze ", "" + ) if (rewardItem == Items.TOOLKIT_4051) { sendDialogue(player, "Your inventory is too full to hold a toolkit.") @@ -700,6 +700,10 @@ class CastleWarsListeners : InteractionListener { sendMessage(player, "The catapult is on fire! Use a bucket of water to extinguish it.") return@on true } + if (CatapultFiringHandler.hasShotInFlight(player)) { + CatapultFiringHandler.sendShotInFlightMessage(player) + return@on true + } if (!inInventory(player, CastleWars.cwRock)) { sendMessage(player, "You need rocks to fire the catapult!") return@on true diff --git a/Server/src/main/content/minigame/castlewars/CatapultConfig.kt b/Server/src/main/content/minigame/castlewars/CatapultConfig.kt index 286dfa969..3bc8fdc4a 100644 --- a/Server/src/main/content/minigame/castlewars/CatapultConfig.kt +++ b/Server/src/main/content/minigame/castlewars/CatapultConfig.kt @@ -56,6 +56,7 @@ object CatapultConfig { const val INACCURACY_RADIUS = CastleWarsConstants.CATAPULT_INACCURACY_RADIUS const val ATTR_LOCATION = "catapult:location" + const val ATTR_SHOT_IN_FLIGHT = "catapult:shot-in-flight" } /** @@ -63,14 +64,10 @@ object CatapultConfig { */ enum class CatapultButton(val buttonId: Int, val deltaX: Int, val deltaY: Int) { LEFT(CatapultConfig.BUTTON_LEFT, -1, 0), RIGHT(CatapultConfig.BUTTON_RIGHT, 1, 0), UP( - CatapultConfig.BUTTON_UP, - 0, - 1 + CatapultConfig.BUTTON_UP, 0, 1 ), DOWN(CatapultConfig.BUTTON_DOWN, 0, -1), FIRE(CatapultConfig.BUTTON_FIRE, 0, 0), CLOSE( - CatapultConfig.BUTTON_CLOSE, - 0, - 0 + CatapultConfig.BUTTON_CLOSE, 0, 0 ), UNKNOWN(-1, 0, 0); diff --git a/Server/src/main/content/minigame/castlewars/CatapultFiringHandler.kt b/Server/src/main/content/minigame/castlewars/CatapultFiringHandler.kt index 8359d4b68..9eb8d1da6 100644 --- a/Server/src/main/content/minigame/castlewars/CatapultFiringHandler.kt +++ b/Server/src/main/content/minigame/castlewars/CatapultFiringHandler.kt @@ -1,5 +1,6 @@ package content.minigame.castlewars +import content.minigame.castlewars.CatapultConfig.ATTR_SHOT_IN_FLIGHT import content.minigame.castlewars.CatapultConfig.GFX_BOULDER_PROJECTILE import content.minigame.castlewars.CatapultConfig.GFX_IMPACT import content.minigame.castlewars.CatapultConfig.IMPACT_RADIUS @@ -28,6 +29,24 @@ import core.tools.RandomFunction */ class CatapultFiringHandler { + companion object { + internal fun hasShotInFlight(player: Player): Boolean { + return getAttribute(player, ATTR_SHOT_IN_FLIGHT, false) + } + + internal fun markShotInFlight(player: Player) { + setAttribute(player, ATTR_SHOT_IN_FLIGHT, true) + } + + internal fun clearShotInFlight(player: Player) { + removeAttribute(player, ATTR_SHOT_IN_FLIGHT) + } + + internal fun sendShotInFlightMessage(player: Player) { + sendMessage(player, "You need to wait for your previous catapult shot to land.") + } + } + /** * Fires the catapult at the specified coordinates. * @param player The player firing the catapult @@ -36,6 +55,13 @@ class CatapultFiringHandler { * @param catapultLocation The actual world location of the catapult */ fun fire(player: Player, coord: Coordinate, team: CatapultTeam, catapultLocation: Location) { + if (hasShotInFlight(player)) { + sendShotInFlightMessage(player) + return + } + + markShotInFlight(player) + val targetLocation = calculateTargetLocation(team, catapultLocation, coord) val finalLocation = applyInaccuracy(targetLocation) @@ -94,22 +120,25 @@ class CatapultFiringHandler { private fun scheduleImpact(player: Player, targetLocation: Location, impactDelayTicks: Int) { submitWorldPulse(object : Pulse(impactDelayTicks) { override fun pulse(): Boolean { - dealImpactDamage(player, targetLocation) + try { + dealImpactDamage(player, targetLocation) + } finally { + clearShotInFlight(player) + } return true } }) } private fun dealImpactDamage(attacker: Player, targetLocation: Location) { - RegionManager.getLocalPlayers(targetLocation, IMPACT_RADIUS).filter { isValidTarget(it, attacker) } - .forEach { target -> + RegionManager.getLocalPlayers(targetLocation, IMPACT_RADIUS).filter { isValidTarget(it) }.forEach { target -> val damage = RandomFunction.random(MIN_DAMAGE, MAX_DAMAGE) target.impactHandler.manualHit(attacker, damage, ImpactHandler.HitsplatType.NORMAL) sendMessage(target, "You are hit by a catapult rock!") } } - private fun isValidTarget(target: Player, attacker: Player): Boolean { + private fun isValidTarget(target: Player): Boolean { return CastleWarsGameState.getPlayerTeam(target) != null } } diff --git a/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt b/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt index 25792e479..a57b531ca 100644 --- a/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt +++ b/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt @@ -5,23 +5,17 @@ import content.minigame.castlewars.areas.CastleWarsGameArea import content.minigame.castlewars.areas.CastleWarsRespawnArea import content.minigame.castlewars.areas.CastleWarsWaitingArea import core.api.* -import core.game.interaction.Option -import core.game.node.entity.skill.Skills +import core.game.component.Component import core.game.global.action.EquipHandler -import core.game.interaction.IntType -import core.game.interaction.InteractionListener -import core.game.interaction.InteractionListeners +import core.game.interaction.* import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills import core.game.node.item.Item import core.game.node.scenery.Scenery import core.game.world.GameWorld import core.game.world.map.Location import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.rs09.consts.Items @@ -40,6 +34,9 @@ class CastleWarsTests { if (InteractionListeners.get(CastleWars.cwExplosivePotion, IntType.ITEM.ordinal, "drop") == null) { CastleWarsItemHandler().defineListeners() } + if (InterfaceListeners.get(CatapultConfig.INTERFACE_ID) == null) { + CastleWarsCatapultInterface().defineInterfaceListeners() + } } } @@ -440,11 +437,7 @@ class CastleWarsTests { player.inventory.add(Item(Items.BRONZE_FULL_HELM_1155)) InteractionListeners.run( - Items.BRONZE_FULL_HELM_1155, - IntType.ITEM, - "wear", - player, - player.inventory[0] + Items.BRONZE_FULL_HELM_1155, IntType.ITEM, "wear", player, player.inventory[0] ) assertNull(player.equipment[EquipmentSlot.HEAD.ordinal]) @@ -459,11 +452,7 @@ class CastleWarsTests { player.inventory.add(Item(Items.HOODED_CLOAK_4041)) InteractionListeners.run( - Items.HOODED_CLOAK_4041, - IntType.ITEM, - "wear", - player, - player.inventory[0] + Items.HOODED_CLOAK_4041, IntType.ITEM, "wear", player, player.inventory[0] ) assertNull(player.equipment[EquipmentSlot.CAPE.ordinal]) @@ -488,11 +477,7 @@ class CastleWarsTests { player.inventory.add(Item(Items.BRONZE_MED_HELM_1139)) InteractionListeners.run( - Items.BRONZE_MED_HELM_1139, - IntType.ITEM, - "wear", - player, - player.inventory[0] + Items.BRONZE_MED_HELM_1139, IntType.ITEM, "wear", player, player.inventory[0] ) assertTrue(didRun) @@ -570,6 +555,59 @@ class CastleWarsTests { assertEquals(-2, zamorakNorth.y - zamorakBase.y) } + @Test + fun catapultShotInFlightBlocksOnlyShooterUntilLanding() { + TestUtils.getMockPlayer("cwcatlock1").use { shooter -> + TestUtils.getMockPlayer("cwcatlock2").use { other -> + val catapult = Scenery( + CastleWars.getOperationalCatapultId(CastleWarsTeam.SARADOMIN), + CastleWarsGameState.saradominCatapultLocation, + 10, + 0 + ) + val operateCatapult = InteractionListeners.get( + catapult.id, IntType.SCENERY.ordinal, "operate" + )!! + + CastleWarsGameArea.saradominPlayers.addAll(listOf(shooter, other)) + shooter.inventory.add(Item(CastleWars.cwRock, 2)) + other.inventory.add(Item(CastleWars.cwRock)) + + operateCatapult(shooter, catapult) + assertTrue(shooter.interfaceManager.hasMainComponent(CatapultConfig.INTERFACE_ID)) + + InterfaceListeners.run( + shooter, Component(CatapultConfig.INTERFACE_ID), 0, CatapultConfig.BUTTON_FIRE, 0, 0 + ) + + assertTrue(CatapultFiringHandler.hasShotInFlight(shooter)) + assertFalse(CatapultFiringHandler.hasShotInFlight(other)) + assertFalse(shooter.interfaceManager.hasMainComponent(CatapultConfig.INTERFACE_ID)) + + val rocksAfterFirstShot = shooter.inventory.getAmount(CastleWars.cwRock) + shooter.setAttribute(CatapultConfig.ATTR_LOCATION, catapult.location) + shooter.interfaceManager.open(Component(CatapultConfig.INTERFACE_ID)) + InterfaceListeners.run( + shooter, Component(CatapultConfig.INTERFACE_ID), 0, CatapultConfig.BUTTON_FIRE, 0, 0 + ) + assertEquals(rocksAfterFirstShot, shooter.inventory.getAmount(CastleWars.cwRock)) + assertFalse(shooter.interfaceManager.hasMainComponent(CatapultConfig.INTERFACE_ID)) + + operateCatapult(shooter, catapult) + assertFalse(shooter.interfaceManager.hasMainComponent(CatapultConfig.INTERFACE_ID)) + + operateCatapult(other, catapult) + assertTrue(other.interfaceManager.hasMainComponent(CatapultConfig.INTERFACE_ID)) + + TestUtils.advanceTicks(10, false) + + assertFalse(CatapultFiringHandler.hasShotInFlight(shooter)) + operateCatapult(shooter, catapult) + assertTrue(shooter.interfaceManager.hasMainComponent(CatapultConfig.INTERFACE_ID)) + } + } + } + @Test fun climbingRopeOnPlayerTileCanBeClimbed() { val rope = Scenery(CastleWars.cwCastleClimbingRope, Location.create(2420, 3078, 1), 4, 0) @@ -602,11 +640,7 @@ class CastleWarsTests { dropper.inventory.add(Item(CastleWars.cwExplosivePotion)) InteractionListeners.run( - CastleWars.cwExplosivePotion, - IntType.ITEM, - "drop", - dropper, - dropper.inventory[0] + CastleWars.cwExplosivePotion, IntType.ITEM, "drop", dropper, dropper.inventory[0] ) assertEquals(35, dropper.skills.lifepoints) @@ -634,7 +668,9 @@ class CastleWarsTests { TestUtils.getMockPlayer("cwbrace2").use { sara2 -> TestUtils.getMockPlayer("cwbracez1").use { zam1 -> TestUtils.getMockPlayer("cwbracez2").use { zam2 -> - braceletPlayer.equipment.replace(Item(Items.CASTLEWAR_BRACE3_11079), EquipmentSlot.HANDS.ordinal) + braceletPlayer.equipment.replace( + Item(Items.CASTLEWAR_BRACE3_11079), EquipmentSlot.HANDS.ordinal + ) CastleWarsWaitingArea.waitingSaradominPlayers.addAll(listOf(braceletPlayer, sara2)) CastleWarsWaitingArea.waitingZamorakPlayers.addAll(listOf(zam1, zam2)) @@ -662,11 +698,7 @@ class CastleWarsTests { player.inventory.add(Item(CastleWars.cwBandages)) InteractionListeners.run( - CastleWars.cwBandages, - IntType.ITEM, - "Heal", - player, - player.inventory[0] + CastleWars.cwBandages, IntType.ITEM, "Heal", player, player.inventory[0] ) assertEquals(59, player.skills.lifepoints)