From 45dd084f5caecd3990c3a90f8ae92affbb963929 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Thu, 16 Sep 2021 01:37:50 -0400 Subject: [PATCH] Implement pitfall trapping for larupias, graahks, and kyatts. --- Server/data/configs/npc_spawns.json | 4 +- .../game/node/entity/impl/ForceMovement.java | 7 +- .../game/node/entity/combat/CombatPulse.kt | 5 +- .../node/entity/combat/CombatSwingHandler.kt | 4 - .../entity/skill/hunter/PolarKebbitHunting.kt | 10 - .../skill/hunter/pitfall/HunterPitfall.kt | 324 ++++++++++++++++++ 6 files changed, 335 insertions(+), 19 deletions(-) delete mode 100644 Server/src/main/kotlin/rs09/game/node/entity/skill/hunter/PolarKebbitHunting.kt create mode 100644 Server/src/main/kotlin/rs09/game/node/entity/skill/hunter/pitfall/HunterPitfall.kt diff --git a/Server/data/configs/npc_spawns.json b/Server/data/configs/npc_spawns.json index f1d0ed544..2606d6874 100644 --- a/Server/data/configs/npc_spawns.json +++ b/Server/data/configs/npc_spawns.json @@ -7209,7 +7209,7 @@ }, { "npc_id": "5105", - "loc_data": "{2780,3017,0,0,0}-{2766,3013,0,0,0}-{2785,3006,0,0,0}-{2775,2996,0,0,0}" + "loc_data": "{2780,3017,0,1,0}-{2766,3013,0,1,0}-{2785,3006,0,1,0}-{2775,2996,0,1,0}" }, { "npc_id": "5109", @@ -10791,4 +10791,4 @@ "npc_id": "1052", "loc_data": "{3420,3442,0,1,0}-{3429,3436,0,1,0}-{3439,3437,0,1,0}-{3448,3444,0,1,0}-{3458,3433,0,1,0}-{3459,3419,0,1,0}-{3449,3420,0,1,0}-{3441,3421,0,1,0}-{3427,3421,0,1,0}-{3414,3422,0,1,0}-{3411,3423,0,1,0}-{3415,3414,0,1,0}-{3410,3409,0,1,0}-{3422,3405,0,1,0}-{3431,3402,0,1,0}-{3444,3405,0,1,0}-{3453,3397,0,1,0}-{3467,3402,0,1,0}-{3473,3395,0,1,0}-{3470,3385,0,1,0}-{3463,3379,0,1,0}-{3451,3380,0,1,0}-{3445,3374,0,1,0}-{3438,3365,0,1,0}-{3429,3367,0,1,0}-{3417,3367,0,1,0}-{3413,3362,0,1,0}-{3417,3358,0,1,0}-{3426,3352,0,1,0}-{3435,3349,0,1,0}-{3442,3353,0,1,0}-{3448,3358,0,1,0}-{3457,3355,0,1,0}-{3461,3348,0,1,0}-{3457,3343,0,1,0}-{3471,3344,0,1,0}-{3426,3338,0,1,0}-{3423,3335,0,1,0}" } -] \ No newline at end of file +] diff --git a/Server/src/main/java/core/game/node/entity/impl/ForceMovement.java b/Server/src/main/java/core/game/node/entity/impl/ForceMovement.java index ba935984e..db76cebf9 100644 --- a/Server/src/main/java/core/game/node/entity/impl/ForceMovement.java +++ b/Server/src/main/java/core/game/node/entity/impl/ForceMovement.java @@ -1,6 +1,7 @@ package core.game.node.entity.impl; import core.game.node.entity.Entity; +import core.game.node.entity.player.Player; import core.game.system.task.Pulse; import rs09.game.world.GameWorld; import core.game.world.map.Direction; @@ -307,7 +308,9 @@ public class ForceMovement extends Pulse { int ticks = 1 + commenceSpeed + pathSpeed; entity.getImpactHandler().setDisabledTicks(ticks); entity.getUpdateMasks().register(new ForceMovementFlag(this)); - entity.getWalkingQueue().updateRegion(destination, false); + if(entity instanceof Player) { + entity.getWalkingQueue().updateRegion(destination, false); + } super.start(); } @@ -476,4 +479,4 @@ public class ForceMovement extends Pulse { public void setEndAnimation(Animation endAnimation) { this.endAnimation = endAnimation; } -} \ No newline at end of file +} diff --git a/Server/src/main/kotlin/rs09/game/node/entity/combat/CombatPulse.kt b/Server/src/main/kotlin/rs09/game/node/entity/combat/CombatPulse.kt index bbcfe6c21..7dcb61a8a 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/combat/CombatPulse.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/combat/CombatPulse.kt @@ -129,6 +129,9 @@ class CombatPulse( if (handler == null) { handler = entity.getSwingHandler(true) } + if (!entity.isAttackable(v, handler!!.type)) { + return true + } if (!swing(entity, victim, handler)) { temporaryHandler = null updateStyle() @@ -461,4 +464,4 @@ class CombatPulse( } } } -} \ No newline at end of file +} diff --git a/Server/src/main/kotlin/rs09/game/node/entity/combat/CombatSwingHandler.kt b/Server/src/main/kotlin/rs09/game/node/entity/combat/CombatSwingHandler.kt index c23a1505c..6bde53a5c 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/combat/CombatSwingHandler.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/combat/CombatSwingHandler.kt @@ -243,10 +243,6 @@ abstract class CombatSwingHandler(var type: CombatStyle?) { if (el.x >= vl.x && el.x < evl.x && el.y >= vl.y && el.y < evl.y || el.z != vl.z) { return InteractionType.NO_INTERACT } - if (!victim.isAttackable(entity, type)) { - entity.properties.combatPulse.stop() - return InteractionType.NO_INTERACT - } return InteractionType.STILL_INTERACT } diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/hunter/PolarKebbitHunting.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/hunter/PolarKebbitHunting.kt deleted file mode 100644 index ce13cd52e..000000000 --- a/Server/src/main/kotlin/rs09/game/node/entity/skill/hunter/PolarKebbitHunting.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* -package core.game.node.entity.skill.hunter - -private const val VARP = 926 -class PolarKebbitHunting { - private enum class TrailType{ - va - } - private enum class Trail(val offset: Int, var ) -}*/ diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/hunter/pitfall/HunterPitfall.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/hunter/pitfall/HunterPitfall.kt new file mode 100644 index 000000000..5f83e7d61 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/hunter/pitfall/HunterPitfall.kt @@ -0,0 +1,324 @@ +import java.util.concurrent.TimeUnit; + +import api.ContentAPI +import core.cache.def.impl.NPCDefinition +import core.cache.def.impl.SceneryDefinition +import core.game.interaction.OptionHandler +import core.game.node.Node +import core.game.node.entity.Entity +import core.game.node.entity.combat.CombatStyle +import core.game.node.entity.impl.Animator.Priority; +import core.game.node.entity.impl.ForceMovement +import core.game.node.entity.npc.AbstractNPC +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.system.task.Pulse +import core.game.world.map.Direction +import core.game.world.map.Location +import core.game.world.update.flag.context.Animation +import core.game.world.update.flag.context.Graphics +import core.plugin.Initializable +import core.plugin.Plugin +import core.tools.RandomFunction +import org.rs09.consts.Items +import org.rs09.consts.NPCs +import rs09.game.interaction.InteractionListener +import rs09.game.world.GameWorld + +/*@Initializable +class HunterPitfall : OptionHandler() { + //19227, + val graahkPitIds = intArrayOf(19227)//, 19268,19267,19266,19264,19265) + val GRAAHK_ID = 5105 + val hunterReq = 41 + override fun handle(player: Player?, node: Node?, option: String?): Boolean { + node ?: return true + player ?: return true + player.sendMessage("Hello from HunterPitfall: ${option}") + when(option) { + "tease" -> { + (node as Entity).attack(player) + } + } + return true + } + + override fun newInstance(arg: Any?): Plugin { + for(graahkPit in graahkPitIds) { + SceneryDefinition.forId(graahkPit).handlers["option:trap"] = this + } + //NPCDefinition.forId(GRAAHK_ID).handlers["option:tease"] = this + return this + } +}*/ + +val LARUPIA_IDS: IntArray = intArrayOf(NPCs.SPINED_LARUPIA_5104) +val GRAAHK_IDS: IntArray = intArrayOf(NPCs.HORNED_GRAAHK_5105, NPCs.HORNED_GRAAHK_5106, NPCs.HORNED_GRAAHK_5107, NPCs.HORNED_GRAAHK_5108) +val KYATT_IDS: IntArray = intArrayOf(NPCs.SABRE_TOOTHED_KYATT_5103, NPCs.SABRE_TOOTHED_KYATT_7497) +val BEAST_IDS: IntArray = intArrayOf(*LARUPIA_IDS, *GRAAHK_IDS, *KYATT_IDS) +val HUNTER_REQS = hashMapOf( + "Spined larupia" to 31, + "Horned graahk" to 41, + "Sabre-toothed kyatt" to 55, + ) +//val pitVarpOffsets = hashMapOf( 19264 to 3, 19265 to 6, 19266 to 9, 19267 to 12, 19268 to 15,) +data class Pit(val varpId: Int, val varpOffset: Int, val horizontal: Boolean) +val pitVarps = hashMapOf( + // Larupia pits (the duplicate 24 is likely authentic) + Location.create(2565,2888) to Pit(917, 27, true), + Location.create(2556,2893) to Pit(917, 18, false), + Location.create(2552,2904) to Pit(917, 24, true), + Location.create(2543,2908) to Pit(917, 21, false), + Location.create(2538,2899) to Pit(917, 24, true), + // Kyatt pits + Location.create(2700,3795) to Pit(917, 0, true), + Location.create(2700,3785) to Pit(917, 3, false), + Location.create(2706,3789) to Pit(917, 6, false), + Location.create(2730,3791) to Pit(917, 9, true), + Location.create(2737,3784) to Pit(917, 12, true), + Location.create(2730,3780) to Pit(917, 15, false), + // Graahk pits + Location.create(2766,3010) to Pit(918, 3, false), + Location.create(2762,3005) to Pit(918, 6, false), + Location.create(2771,3004) to Pit(918, 9, true), + Location.create(2777,3001) to Pit(918, 12, false), + Location.create(2784,3001) to Pit(918, 15, true), + ) +/*val pitJumpSpots = hashMapOf( + Location.create(2766,3010) to hashMapOf( + Location.create(2766,3009) to Direction.NORTH, + Location.create(2767,3009) to Direction.NORTH, + Location.create(2766,3012) to Direction.SOUTH, + Location.create(2767,3012) to Direction.SOUTH, + ), + Location.create(2762,3005) to hashMapOf( + Location.create(2762,3004) to Direction.NORTH, + Location.create(2763,3004) to Direction.NORTH, + Location.create(2762,3007) to Direction.SOUTH, + Location.create(2763,3007) to Direction.SOUTH, + ), + Location.create(2771,3004) to hashMapOf( + Location.create(2770,3004) to Direction.EAST, + Location.create(2770,3005) to Direction.EAST, + Location.create(2773,3004) to Direction.WEST, + Location.create(2773,3005) to Direction.WEST, + ), + Location.create(2777,3001) to hashMapOf( + Location.create(2777,3000) to Direction.NORTH, + Location.create(2778,3000) to Direction.NORTH, + Location.create(2777,3003) to Direction.SOUTH, + Location.create(2778,3003) to Direction.SOUTH, + ), + Location.create(2784,3001) to hashMapOf( + Location.create(2783,3002) to Direction.EAST, + Location.create(2783,3001) to Direction.EAST, + Location.create(2786,3002) to Direction.WEST, + Location.create(2786,3001) to Direction.WEST, + ), + )*/ +fun pitJumpSpots(loc: Location): HashMap? { + val pit = pitVarps[loc] ?: return null + if(pit.horizontal) { + return hashMapOf( + loc.transform(-1, 0, 0) to Direction.EAST, + loc.transform(-1, 1, 0) to Direction.EAST, + loc.transform(2, 0, 0) to Direction.WEST, + loc.transform(2, 1, 0) to Direction.WEST, + ) + } else { + return hashMapOf( + loc.transform(0, -1, 0) to Direction.NORTH, + loc.transform(1, -1, 0) to Direction.NORTH, + loc.transform(0, 2, 0) to Direction.SOUTH, + loc.transform(1, 2, 0) to Direction.SOUTH, + ) + } +} + +val KNIFE = Item(Items.KNIFE_946) +val TEASING_STICK = Item(Items.TEASING_STICK_10029) +val LOGS = Item(Items.LOGS_1511) + +val PIT = 19227 +val SPIKED_PIT = 19228 +val GRAAHK_PIT = 19231 +val LARUPIA_PIT = 19232 +val KYATT_PIT = 19233 + +class PitfallListeners : InteractionListener() { + + override fun defineListeners() { + setDest(SCENERY, intArrayOf(PIT, SPIKED_PIT, LARUPIA_PIT, GRAAHK_PIT, KYATT_PIT), "trap", "jump", "dismantle") { player, node -> + val pit = node as Scenery + val src = player.getLocation() + var dst = pit.getLocation() + val locs = pitJumpSpots(dst) + if(locs != null) { + for(loc in locs.keys) { + if(src.getDistance(loc) <= src.getDistance(dst)) { + dst = loc + } + } + } else { + if(player is Player) { + player.sendMessage("Error: Unimplemented pit at ${pit.location}") + } + } + return@setDest dst + } + on(PIT, SCENERY, "trap") { player, node -> + val pit = node as Scenery; + // TODO: check hunter level, remove logs + if(player.skills.getLevel(Skills.HUNTER) < 31) { + player.sendMessage("You need a hunter level of 31 to set a pitfall trap.") + return@on true + } + + val maxTraps = player.hunterManager.maximumTraps + if(player.getAttribute("pitfall:count", 0) >= maxTraps) { + player.sendMessage("You can't set up more than ${maxTraps} pitfall traps at your hunter level.") + return@on true + } + player.incrementAttribute("pitfall:count", 1) + + if(!player.inventory.containsItem(KNIFE) || !player.inventory.remove(LOGS)) { + player.sendMessage("You need some logs and a knife to set a pitfall trap.") + return@on true + } + + player.setAttribute("pitfall:timestamp:${pit.location.x}:${pit.location.y}", System.currentTimeMillis()) + setPitState(player, pit.location, 1) + val collapsePulse = object : Pulse(201, player) { + override fun pulse(): Boolean { + val oldTime = player.getAttribute("pitfall:timestamp:${pit.location.x}:${pit.location.y}", System.currentTimeMillis()) + if(System.currentTimeMillis() - oldTime >= TimeUnit.MINUTES.toMillis(2)) { + player.sendMessage("Your pitfall trap has collapsed.") + setPitState(player, pit.location, 0) + player.incrementAttribute("pitfall:count", -1) + } + return true + } + } + GameWorld.Pulser.submit(collapsePulse) + return@on true + } + on(SPIKED_PIT, SCENERY, "jump") { player, node -> + val pit = node as Scenery; + val src = player.getLocation() + val dir = pitJumpSpots(pit.getLocation())!![src] + if(dir != null) { + val dst = src.transform(dir, 3) + ForceMovement.run(player, src, dst, ForceMovement.WALK_ANIMATION, Animation(1603), dir, 16); + val pitfall_npc: Entity? = player.getAttribute("pitfall_npc", null) + if(pitfall_npc != null && pitfall_npc.getLocation().getDistance(src) < 3.0) { + val last_pit_loc: Location? = pitfall_npc.getAttribute("last_pit_loc", null) + if(last_pit_loc == pit.location) { + player.sendMessage("The ${pitfall_npc.name.toLowerCase()} won't jump the same pit twice in a row.") + return@on true + } + // TODO: what are the actual probabilities of a graahk jumping over a pit? + val chance = RandomFunction.getSkillSuccessChance(50.0, 100.0, player.skills.getLevel(Skills.HUNTER)) + if(RandomFunction.random(0.0, 100.0) < chance) { + //ForceMovement.run(pitfall_npc, pitfall_npc.getLocation(), pit.getLocation(), ForceMovement.WALK_ANIMATION, Animation(ANIM), dir, 8); + //pitfall_npc.setLocation(pit.getLocation()); + //pitfall_npc.walkingQueue.addPath(pit.location.x, pit.location.y); + ContentAPI.teleport(pitfall_npc, pit.location) + pitfall_npc.startDeath(null) + player.removeAttribute("pitfall:timestamp:${pit.location.x}:${pit.location.y}") + player.incrementAttribute("pitfall:count", -1) + setPitState(player, pit.location, 3) + //pitfall_npc.animate(Animation(5234)) + } else { + //ForceMovement.run(pitfall_npc, pitfall_npc.getLocation(), dst, ForceMovement.WALK_ANIMATION, Animation(ANIM), dir, 8); + //pitfall_npc.walkingQueue.addPath(npcdst.x, npcdst.y) + val npcdst = dst.transform(dir, if(dir == Direction.SOUTH || dir == Direction.WEST) 1 else 0) + ContentAPI.teleport(pitfall_npc, npcdst) + pitfall_npc.animate(Animation(5232, Priority.HIGH)) + pitfall_npc.attack(player) + pitfall_npc.setAttribute("last_pit_loc", pit.location) + } + } + } + return@on true + } + on(SPIKED_PIT, SCENERY, "dismantle") { player, node -> + val pit = node as Scenery; + player.removeAttribute("pitfall:timestamp:${pit.location.x}:${pit.location.y}") + player.incrementAttribute("pitfall:count", -1) + setPitState(player, pit.location, 0) + return@on true + } + on(LARUPIA_PIT, SCENERY, "dismantle") { player, node -> + lootCorpse(player, node as Scenery, 180.0, Items.LARUPIA_FUR_10095, Items.TATTY_LARUPIA_FUR_10093) + return@on true + } + on(GRAAHK_PIT, SCENERY, "dismantle") { player, node -> + lootCorpse(player, node as Scenery, 240.0, Items.GRAAHK_FUR_10099, Items.TATTY_GRAAHK_FUR_10097) + return@on true + } + on(KYATT_PIT, SCENERY, "dismantle") { player, node -> + lootCorpse(player, node as Scenery, 300.0, Items.KYATT_FUR_10103, Items.TATTY_KYATT_FUR_10101) + return@on true + } + on(BEAST_IDS, NPC, "tease") { player, node -> + val entity = node as Entity + val hunterReq = HUNTER_REQS[entity.name]!! + if(player.skills.getLevel(Skills.HUNTER) < hunterReq) { + player.sendMessage("You need a hunter level of ${hunterReq} to hunt ${entity.name.toLowerCase()}s.") + return@on true + } + if(!player.inventory.containsItem(TEASING_STICK)) { + player.sendMessage("You need a teasing stick to hunt ${entity.name.toLowerCase()}s.") + return@on true + } + entity.attack(player) + player.setAttribute("pitfall_npc", entity) + return@on true + } + } + + fun lootCorpse(player: Player, pit: Scenery, xp: Double, goodFur: Int, badFur: Int) { + if(player.inventory.freeSlots() < 2) { + player.sendMessage("You don't have enough inventory space. You need 2 more free slots."); + return + } + setPitState(player, pit.location, 0) + player.getSkills().addExperience(Skills.HUNTER, xp, true); + player.inventory.add(Item(Items.BIG_BONES_532)) + // TODO: what's the actual probability of tatty vs perfect fur? + val chance = RandomFunction.getSkillSuccessChance(50.0, 100.0, player.skills.getLevel(Skills.HUNTER)) + if(RandomFunction.random(0.0, 100.0) < chance) { + player.inventory.add(Item(goodFur)) + } else { + player.inventory.add(Item(badFur)) + } + } + + fun setPitState(player: Player, loc: Location, state: Int) { + val pit = pitVarps[loc]!! + player.varpManager.get(pit.varpId).setVarbit(pit.varpOffset, state).send(player) + } +} + +@Initializable +class PitfallNPC : AbstractNPC { + constructor() : super(NPCs.HORNED_GRAAHK_5105, null, true) {} + private constructor(id: Int, location: Location) : super(id, location) {} + override fun construct(id: Int, location: Location, vararg objects: Any?): AbstractNPC { + return PitfallNPC(id, location) + } + + init { + walkRadius = 22 + } + + override fun getIds(): IntArray { + return BEAST_IDS + } + + override fun isAttackable(entity: Entity, style: CombatStyle): Boolean { + return entity is Player + } +}