diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsConstants.kt b/Server/src/main/content/minigame/castlewars/CastleWarsConstants.kt index e46a16e60..7d9b92cf1 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsConstants.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsConstants.kt @@ -86,14 +86,14 @@ object CastleWarsConstants { const val ENERGY_BARRIER_DRAIN = 4.0 // ==================== Backdoor Pick-Lock ==================== - /** Thieving level required for pick-lock (1 = no real requirement, all players can attempt) */ - const val BACKDOOR_PICKLOCK_LEVEL = 1 + /** Thieving level required for pick-lock, matching Knight of Ardougne pickpocketing */ + const val BACKDOOR_PICKLOCK_LEVEL = 55 - /** Base pick-lock success chance (out of 100) at minimum level */ - const val BACKDOOR_PICKLOCK_BASE_CHANCE = 50 + /** Low roll bound for pick-lock success, matching Knight of Ardougne pickpocketing */ + const val BACKDOOR_PICKLOCK_LOW = 50.0 - /** Maximum pick-lock success chance (out of 100) */ - const val BACKDOOR_PICKLOCK_MAX_CHANCE = 95 + /** High roll bound for pick-lock success, matching Knight of Ardougne pickpocketing */ + const val BACKDOOR_PICKLOCK_HIGH = 240.0 /** Ticks to lock player during pick-lock attempt */ const val BACKDOOR_PICKLOCK_TICKS = 3 diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsItemHandler.kt b/Server/src/main/content/minigame/castlewars/CastleWarsItemHandler.kt index f6872e90c..ac40ff057 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsItemHandler.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsItemHandler.kt @@ -219,8 +219,6 @@ class CastleWarsItemHandler : InteractionListener { } cleanupDeadBarricades() - dealExplosiveAoeDamage(player, player.location) - return@on true } @@ -348,4 +346,3 @@ class CastleWarsItemHandler : InteractionListener { .filter { it.id in barricadeIds && it.isActive } } } - diff --git a/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt index 6845467f4..56ce42fc0 100644 --- a/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt +++ b/Server/src/main/content/minigame/castlewars/CastleWarsListeners.kt @@ -15,6 +15,7 @@ import core.game.global.action.SpecialLadders import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills import core.game.node.entity.player.link.TeleportManager import core.game.node.item.Item import core.game.node.scenery.Scenery @@ -27,6 +28,7 @@ import core.game.world.map.RegionManager import core.game.world.update.flag.chunk.ObjectUpdateFlag import core.game.world.update.flag.context.Animation import core.tools.Log +import core.tools.RandomFunction import org.rs09.consts.Components import org.rs09.consts.Items import org.rs09.consts.Sounds @@ -48,24 +50,24 @@ private val barrierCoordinates: Map> = mapOf( BarrierData( Location.create(2373, 3127, 1), Direction.WEST, Location.create(2373, 3126, 1), - Location.create(2373, 3128, 1) + Location.create(2373, 3127, 1) ), BarrierData( Location.create(2376, 3131, 1), Direction.SOUTH, Location.create(2377, 3131, 1), - Location.create(2375, 3131, 1) + Location.create(2376, 3131, 1) ) ), CastleWars.saradominEnergyBarrier to listOf( BarrierData( Location.create(2426, 3080, 1), Direction.EAST, Location.create(2426, 3081, 1), - Location.create(2426, 3079, 1) + Location.create(2426, 3080, 1) ), BarrierData( Location.create(2423, 3076, 1), Direction.NORTH, Location.create(2422, 3076, 1), - Location.create(2424, 3076, 1) + Location.create(2423, 3076, 1) ) ) ) @@ -142,6 +144,9 @@ internal fun isOnClimbingRopeBaseSide(playerLoc: Location, rope: Scenery): Boole } internal fun getClimbingRopeInteractionDestination(playerLoc: Location, rope: Scenery): Location { + if (playerLoc == rope.location) { + return playerLoc + } val traversal = getClimbingRopeTraversal(rope) return if (isOnClimbingRopeBaseSide(playerLoc, rope)) { traversal.baseOfWallTile @@ -151,7 +156,15 @@ internal fun getClimbingRopeInteractionDestination(playerLoc: Location, rope: Sc } internal fun canClimbUpClimbingRope(playerLoc: Location, rope: Scenery): Boolean { - return playerLoc == getClimbingRopeTraversal(rope).baseOfWallTile + return playerLoc == rope.location || playerLoc == getClimbingRopeTraversal(rope).baseOfWallTile +} + +internal fun getBackdoorPicklockSuccessChance(thievingLevel: Int): Double { + return RandomFunction.getSkillSuccessChance( + CastleWarsConstants.BACKDOOR_PICKLOCK_LOW, + CastleWarsConstants.BACKDOOR_PICKLOCK_HIGH, + thievingLevel + ) } class CastleWarsListeners : InteractionListener { @@ -567,19 +580,21 @@ class CastleWarsListeners : InteractionListener { sendMessage(player, "You can't pick the lock from this side.") return@on true } + val thievingLevel = player.skills.getLevel(Skills.THIEVING) + if (thievingLevel < CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL) { + sendMessage( + player, + "You need a Thieving level of ${CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL} to pick this lock." + ) + return@on true + } lock(player, CastleWarsConstants.BACKDOOR_PICKLOCK_TICKS) animate(player, 2246) sendMessage(player, "You attempt to pick the lock...") - val thievingLevel = player.skills.getLevel(core.game.node.entity.skill.Skills.THIEVING) - val successChance = (CastleWarsConstants.BACKDOOR_PICKLOCK_BASE_CHANCE + - (thievingLevel - CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL)) - .coerceIn( - CastleWarsConstants.BACKDOOR_PICKLOCK_BASE_CHANCE, - CastleWarsConstants.BACKDOOR_PICKLOCK_MAX_CHANCE - ) + val successChance = getBackdoorPicklockSuccessChance(thievingLevel) runTask(player, CastleWarsConstants.BACKDOOR_PICKLOCK_TICKS) { - if ((1..100).random() <= successChance) { + if (RandomFunction.randomDouble(1.0, 100.0) <= successChance) { openBackdoor(player, door) sendMessage(player, "You successfully pick the lock!") } else { @@ -589,7 +604,7 @@ class CastleWarsListeners : InteractionListener { return@on true } - on(CastleWars.backdoorOpenIds, IntType.SCENERY, "Close") { player, node -> + on(CastleWars.backdoorOpenIds, IntType.SCENERY, "Close", "Lock") { player, node -> val door = node.asScenery() val doorTeam = if (door.id == CastleWars.saradominBackdoorOpen) CastleWarsTeam.SARADOMIN else CastleWarsTeam.ZAMORAK diff --git a/Server/src/main/content/minigame/castlewars/CatapultConfig.kt b/Server/src/main/content/minigame/castlewars/CatapultConfig.kt index 5e66854ed..6a899a88b 100644 --- a/Server/src/main/content/minigame/castlewars/CatapultConfig.kt +++ b/Server/src/main/content/minigame/castlewars/CatapultConfig.kt @@ -35,7 +35,7 @@ object CatapultConfig { const val DEFAULT_X = CastleWarsConstants.CATAPULT_DEFAULT_X const val DEFAULT_Y = CastleWarsConstants.CATAPULT_DEFAULT_Y const val CENTER_OFFSET = 15 - const val HORIZONTAL_TILE_SCALE = 2 + const val TILE_SCALE = 2 const val SOUND_FIRE = 1440 const val SOUND_IMPACT = 851 @@ -82,21 +82,21 @@ enum class CatapultButton(val buttonId: Int, val deltaX: Int, val deltaY: Int) { enum class CatapultTeam { SARADOMIN { override fun calculateTargetX(catapultX: Int, interfaceX: Int): Int { - return catapultX + ((interfaceX - CatapultConfig.CENTER_OFFSET) * CatapultConfig.HORIZONTAL_TILE_SCALE) + return catapultX + ((interfaceX - CatapultConfig.CENTER_OFFSET) * CatapultConfig.TILE_SCALE) } override fun calculateTargetY(catapultY: Int, interfaceY: Int): Int { - return catapultY + interfaceY + return catapultY + (interfaceY * CatapultConfig.TILE_SCALE) } }, ZAMORAK { override fun calculateTargetX(catapultX: Int, interfaceX: Int): Int { - return catapultX - ((interfaceX - CatapultConfig.CENTER_OFFSET) * CatapultConfig.HORIZONTAL_TILE_SCALE) + return catapultX - ((interfaceX - CatapultConfig.CENTER_OFFSET) * CatapultConfig.TILE_SCALE) } override fun calculateTargetY(catapultY: Int, interfaceY: Int): Int { - return catapultY - interfaceY + return catapultY - (interfaceY * CatapultConfig.TILE_SCALE) } }; diff --git a/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt b/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt index 55394bbb5..dba17d5f4 100644 --- a/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt +++ b/Server/src/test/kotlin/content/minigame/castlewars/CastleWarsTests.kt @@ -3,16 +3,20 @@ package content.minigame.castlewars import TestUtils import content.minigame.castlewars.areas.CastleWarsGameArea import content.minigame.castlewars.areas.CastleWarsWaitingArea +import content.global.skill.thieving.Pickpockets import core.api.EquipmentSlot +import core.game.node.entity.skill.Skills import core.game.global.action.EquipHandler import core.game.interaction.IntType import core.game.interaction.InteractionListener import core.game.interaction.InteractionListeners import core.game.node.item.Item +import core.game.node.scenery.Scenery 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.Test @@ -26,6 +30,13 @@ class CastleWarsTests { if (InteractionListeners.get("equip", IntType.ITEM.ordinal) == null) { EquipHandler().defineListeners() } + if (InteractionListeners.get(CastleWars.saradominBackdoorOpen, IntType.SCENERY.ordinal, "lock") == null) { + CastleWarsListeners().defineDestinationOverrides() + CastleWarsListeners().defineListeners() + } + if (InteractionListeners.get(CastleWars.cwExplosivePotion, IntType.ITEM.ordinal, "drop") == null) { + CastleWarsItemHandler().defineListeners() + } } } @@ -176,4 +187,91 @@ class CastleWarsTests { } } } + + @Test + fun ownTeamBackdoorLockOptionIsHandled() { + assertNotNull( + InteractionListeners.get(CastleWars.saradominBackdoorOpen, IntType.SCENERY.ordinal, "lock") + ) + assertNotNull( + InteractionListeners.get(CastleWars.zamorakBackdoorOpen, IntType.SCENERY.ordinal, "lock") + ) + } + + @Test + fun backdoorPicklockMatchesKnightOfArdougneDifficulty() { + TestUtils.getMockPlayer("cwthief").use { player -> + player.skills.setStaticLevel(Skills.THIEVING, CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL) + player.skills.setLevel(Skills.THIEVING, CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL) + + assertEquals(Pickpockets.KNIGHT_OF_ADROUGNE.requiredLevel, CastleWarsConstants.BACKDOOR_PICKLOCK_LEVEL) + assertEquals( + Pickpockets.KNIGHT_OF_ADROUGNE.getSuccessChance(player), + getBackdoorPicklockSuccessChance(player.skills.getLevel(Skills.THIEVING)) + ) + } + } + + @Test + fun catapultInterfaceCoordinatesScaleByTwoTilesOnBothAxes() { + val handler = CatapultFiringHandler() + val catapult = Location.create(2400, 3100, 0) + + val saradominBase = handler.calculateTargetLocation(CatapultTeam.SARADOMIN, catapult, Coordinate(8, 13)) + val saradominEast = handler.calculateTargetLocation(CatapultTeam.SARADOMIN, catapult, Coordinate(9, 13)) + val saradominNorth = handler.calculateTargetLocation(CatapultTeam.SARADOMIN, catapult, Coordinate(8, 14)) + assertEquals(2, saradominEast.x - saradominBase.x) + assertEquals(2, saradominNorth.y - saradominBase.y) + + val zamorakBase = handler.calculateTargetLocation(CatapultTeam.ZAMORAK, catapult, Coordinate(8, 13)) + val zamorakEast = handler.calculateTargetLocation(CatapultTeam.ZAMORAK, catapult, Coordinate(9, 13)) + val zamorakNorth = handler.calculateTargetLocation(CatapultTeam.ZAMORAK, catapult, Coordinate(8, 14)) + assertEquals(-2, zamorakEast.x - zamorakBase.x) + assertEquals(-2, zamorakNorth.y - zamorakBase.y) + } + + @Test + fun climbingRopeOnPlayerTileCanBeClimbed() { + val rope = Scenery(CastleWars.cwCastleClimbingRope, Location.create(2420, 3078, 1), 4, 0) + + assertEquals(rope.location, getClimbingRopeInteractionDestination(rope.location, rope)) + assertTrue(canClimbUpClimbingRope(rope.location, rope)) + } + + @Test + fun respawnEnergyBarrierPassesAcrossOneWallTile() { + val barrierTile = Location.create(2426, 3080, 1) + val outsideTile = Location.create(2426, 3081, 1) + val barrier = BarrierData(barrierTile, core.game.world.map.Direction.EAST, outsideTile, barrierTile) + + assertEquals(outsideTile to barrierTile, getBarrierPassTiles(outsideTile, barrier)) + assertEquals(barrierTile to outsideTile, getBarrierPassTiles(barrierTile, barrier)) + } + + @Test + fun droppingExplosivePotionOnlyDamagesDropper() { + TestUtils.getMockPlayer("cwexploder").use { dropper -> + TestUtils.getMockPlayer("cwtarget").use { target -> + dropper.location = Location.create(2400, 3100, 0) + target.location = Location.create(2401, 3100, 0) + dropper.skills.lifepoints = 50 + target.skills.lifepoints = 50 + CastleWarsGameArea.ticksLeftInGame = 100 + CastleWarsGameArea.saradominPlayers.add(dropper) + CastleWarsGameArea.zamorakPlayers.add(target) + dropper.inventory.add(Item(CastleWars.cwExplosivePotion)) + + InteractionListeners.run( + CastleWars.cwExplosivePotion, + IntType.ITEM, + "drop", + dropper, + dropper.inventory[0] + ) + + assertEquals(35, dropper.skills.lifepoints) + assertEquals(50, target.skills.lifepoints) + } + } + } }