Catapult cooldown

Player who fires catapult has to wait for their shot to land before being able to operate catapult again
This commit is contained in:
dam 2026-05-12 20:24:00 +03:00
parent 7d72b4b09d
commit eca0f81a1c
No known key found for this signature in database
GPG key ID: 4AF4E722399663FB
5 changed files with 115 additions and 48 deletions

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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
}
}

View file

@ -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)