Implemented Maze random event
This commit is contained in:
@@ -8,6 +8,7 @@ import content.global.ame.events.drunkendwarf.DrunkenDwarfNPC
|
||||
import content.global.ame.events.evilbob.EvilBobNPC
|
||||
import content.global.ame.events.evilchicken.EvilChickenNPC
|
||||
import content.global.ame.events.freakyforester.FreakyForesterNPC
|
||||
import content.global.ame.events.maze.MazeNPC
|
||||
import content.global.ame.events.genie.GenieNPC
|
||||
import content.global.ame.events.pillory.PilloryNPC
|
||||
import content.global.ame.events.rickturpentine.RickTurpentineNPC
|
||||
@@ -45,6 +46,7 @@ enum class RandomEvents(val npc: RandomEventNPC, val loot: WeightBasedTable? = n
|
||||
WeightedItem(Items.TOOTH_HALF_OF_A_KEY_985,1,1,0.1),
|
||||
WeightedItem(Items.LOOP_HALF_OF_A_KEY_987,1,1,0.1)
|
||||
)),
|
||||
MAZE(npc = MazeNPC()),
|
||||
DRILL_DEMON(npc = SeargentDamienNPC()),
|
||||
EVIL_CHICKEN(npc = EvilChickenNPC()),
|
||||
STRANGE_PLANT(npc = StrangePlantNPC()),
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
package content.global.ame.events.maze
|
||||
|
||||
import core.api.*
|
||||
import core.api.utils.WeightBasedTable
|
||||
import core.api.utils.WeightedItem
|
||||
import core.game.event.EventHook
|
||||
import core.game.event.TickEvent
|
||||
import core.game.global.action.DoorActionHandler
|
||||
import core.game.interaction.IntType
|
||||
import core.game.interaction.InteractionListener
|
||||
import core.game.interaction.QueueStrength
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.scenery.SceneryBuilder
|
||||
import core.game.system.task.Pulse
|
||||
import core.game.world.GameWorld.Pulser
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.zone.ZoneBorders
|
||||
import core.game.world.map.zone.ZoneRestriction
|
||||
import core.game.world.update.flag.context.Graphics
|
||||
import org.rs09.consts.*
|
||||
|
||||
class MazeInterface : InteractionListener, EventHook<TickEvent>, MapArea {
|
||||
companion object {
|
||||
const val MAZE_TIMER_INTERFACE = Components.MAZETIMER_209
|
||||
const val MAZE_TIMER_VARP = 531 // Interface 209 child 2 config: [531, 0]
|
||||
const val MAZE_ATTRIBUTE_RETURN_LOC = "/save:original-loc"
|
||||
const val MAZE_ATTRIBUTE_TICKS_LEFT = "maze:percent-ticks-left"
|
||||
const val MAZE_ATTRIBUTE_CHESTS_OPEN = "/save:maze:chests-opened"
|
||||
|
||||
val STARTING_POINTS = arrayOf(
|
||||
Location(2928, 4553, 0),
|
||||
Location(2917, 4553, 0),
|
||||
Location(2908, 4555, 0),
|
||||
Location(2891, 4589, 0),
|
||||
Location(2891, 4595, 0),
|
||||
Location(2891, 4595, 0),
|
||||
Location(2926, 4597, 0),
|
||||
Location(2931, 4597, 0),
|
||||
// There's 2 more, but there isn't a door for them...
|
||||
)
|
||||
|
||||
val REWARD_ITEM = intArrayOf(
|
||||
Items.COINS_995,
|
||||
Items.FEATHER_314,
|
||||
Items.IRON_ARROW_884,
|
||||
Items.CHAOS_RUNE_562,
|
||||
Items.STEEL_ARROW_886,
|
||||
Items.DEATH_RUNE_560,
|
||||
Items.COAL_454,
|
||||
Items.NATURE_RUNE_561,
|
||||
Items.MITHRIL_ORE_448
|
||||
)
|
||||
|
||||
val ITEM_DIVISOR = arrayOf(
|
||||
1.0,
|
||||
2.0,
|
||||
3.0,
|
||||
9.0,
|
||||
12.0,
|
||||
18.0,
|
||||
45.0,
|
||||
162.0,
|
||||
180.0,
|
||||
)
|
||||
|
||||
val CHEST_REWARDS = WeightBasedTable.create(
|
||||
WeightedItem(Items.AIR_RUNE_556,15,15,1.0),
|
||||
WeightedItem(Items.WATER_RUNE_555,10,10,1.0),
|
||||
WeightedItem(Items.EARTH_RUNE_557,10,10,1.0),
|
||||
WeightedItem(Items.FIRE_RUNE_554,10,10,1.0),
|
||||
WeightedItem(Items.BRONZE_ARROW_882,20,20,1.0),
|
||||
WeightedItem(Items.BRONZE_BOLTS_877,10,10,1.0),
|
||||
WeightedItem(Items.IRON_ARROW_884,15,15,1.0),
|
||||
WeightedItem(Items.ATTACK_POTION2_123,1,1,1.0),
|
||||
WeightedItem(Items.STRENGTH_POTION2_117,1,1,1.0),
|
||||
WeightedItem(Items.DEFENCE_POTION2_135,1,1,1.0),
|
||||
)
|
||||
|
||||
fun initMaze(player: Player) {
|
||||
setAttribute(player, MAZE_ATTRIBUTE_TICKS_LEFT, 300)
|
||||
setVarp(player, MAZE_TIMER_VARP, (getAttribute<Int>(player, MAZE_ATTRIBUTE_TICKS_LEFT, 0) / 3),false)
|
||||
openOverlay(player, MAZE_TIMER_INTERFACE)
|
||||
sendMessage(player, "You need to reach the maze center, then you'll be returned to where you were.")
|
||||
sendNPCDialogue(player, NPCs.MYSTERIOUS_OLD_MAN_410, "You need to reach the maze center, then you'll be returned to where you were.")
|
||||
}
|
||||
|
||||
fun calculateLoot(player: Player) {
|
||||
val randomNumber = (0..8).random()
|
||||
val totalLevel = player.getSkills().totalLevel.toDouble()
|
||||
val rewardPotential = getAttribute(player, MAZE_ATTRIBUTE_TICKS_LEFT, 0).toDouble() / 300.0
|
||||
val itemDivisor = ITEM_DIVISOR[randomNumber]
|
||||
val itemQuantity = (totalLevel * rewardPotential * 3.33) / itemDivisor
|
||||
// sendMessage(player, "Maze reward calculation: $totalLevel * $rewardPotential * 3.33 / $itemDivisor = $itemQuantity")
|
||||
if (itemQuantity.toInt() > 0) {
|
||||
addItemOrDrop(player, REWARD_ITEM[randomNumber], itemQuantity.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Chest Location to rotation mapping.
|
||||
* This is needed as it is impossible to obtain the underlying chest scenery for the rotation.
|
||||
* 0: Facing North, 1: Facing East, 2: Facing South, 3: Facing West
|
||||
*/
|
||||
val chestLocationRotationMap = mapOf(
|
||||
Location(2930, 4595, 0).toString() to 2,
|
||||
Location(2924, 4572, 0).toString() to 2,
|
||||
Location(2925, 4573, 0).toString() to 0,
|
||||
Location(2900, 4578, 0).toString() to 2,
|
||||
Location(2901, 4560, 0).toString() to 1,
|
||||
Location(2890, 4599, 0).toString() to 2,
|
||||
Location(2896, 4591, 0).toString() to 2,
|
||||
Location(2895, 4592, 0).toString() to 1,
|
||||
Location(2901, 4560, 0).toString() to 3,
|
||||
Location(2918, 4590, 0).toString() to 1,
|
||||
Location(2917, 4590, 0).toString() to 3,
|
||||
)
|
||||
|
||||
/**
|
||||
* Chest Interaction workaround
|
||||
*
|
||||
* The issue here is that the walls(3626) of the Maze are overlapping some(not all) chest sceneries.
|
||||
*
|
||||
* The types for the wallScenery:
|
||||
* Type 0 - flat panel |
|
||||
* Type 2 - right angle panel with rotation 0:r 1:7 2:> 3:L
|
||||
* Type 3 - corner post . for angle edges of walls
|
||||
*/
|
||||
fun overrideScenery(wallScenery: core.game.node.scenery.Scenery, chestSceneryId: Int): core.game.node.scenery.Scenery {
|
||||
if (wallScenery.id == chestSceneryId) {
|
||||
replaceScenery(wallScenery, Scenery.CHEST_3636, 30)
|
||||
wallScenery.isActive = true
|
||||
return wallScenery // Return the chest scenery as the wallScenery isn't there.
|
||||
}
|
||||
|
||||
addScenery(Scenery.CHEST_3636, wallScenery.location, chestLocationRotationMap[wallScenery.location.toString()] ?: 0, 10)
|
||||
addScenery(wallScenery)
|
||||
// replaceScenery(newChestScenery, Scenery.CHEST_3636, 3) // didn't work for an underlying scenery
|
||||
// I did a world pulse since everyone will get to see the chest open.
|
||||
Pulser.submit(object : Pulse(30) {
|
||||
override fun pulse(): Boolean {
|
||||
addScenery(Scenery.CHEST_3635, wallScenery.location, chestLocationRotationMap[wallScenery.location.toString()] ?: 0, 10)
|
||||
addScenery(wallScenery)
|
||||
return true
|
||||
}
|
||||
})
|
||||
// Return the chest scenery to replace PacketProcessor so that MISMATCH will not happen.
|
||||
return core.game.node.scenery.Scenery(
|
||||
chestSceneryId,
|
||||
wallScenery.location,
|
||||
chestLocationRotationMap[wallScenery.location.toString()] ?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun defineListeners() {
|
||||
|
||||
// This somehow doesn't trigger as the scenery.id != objId (3626 != 3635)
|
||||
on(Scenery.CHEST_3635, IntType.SCENERY, "open") { player, node ->
|
||||
if (getAttribute(player, MAZE_ATTRIBUTE_TICKS_LEFT, 0) > 0 && getAttribute(player, MAZE_ATTRIBUTE_CHESTS_OPEN, 0) < 10) {
|
||||
animate(player, 536)
|
||||
// val actualScenery = RegionManager.getObject(node.location.z, node.location.x, node.location.y, 3626)
|
||||
val tableRoll = CHEST_REWARDS.roll()
|
||||
addItemOrBank(player, tableRoll[0].id)
|
||||
when (tableRoll[0].id){
|
||||
Items.AIR_RUNE_556 -> sendItemDialogue(player, Items.AIR_RUNE_556, "You've found some air runes!")
|
||||
Items.WATER_RUNE_555 -> sendItemDialogue(player, Items.WATER_RUNE_555, "You've found some water runes!")
|
||||
Items.EARTH_RUNE_557 -> sendItemDialogue(player, Items.EARTH_RUNE_557, "You've found some earth runes!")
|
||||
Items.FIRE_RUNE_554 -> sendItemDialogue(player, Items.FIRE_RUNE_554, "You've found some fire runes!")
|
||||
Items.BRONZE_ARROW_882 -> sendItemDialogue(player, Items.BRONZE_ARROW_882, "You've found some bronze arrows!")
|
||||
Items.BRONZE_BOLTS_877 -> sendItemDialogue(player, Items.BRONZE_BOLTS_877, "You've found some bronze bolts!")
|
||||
Items.IRON_ARROW_884 -> sendItemDialogue(player, Items.IRON_ARROW_884, "You've found some iron arrows!")
|
||||
Items.ATTACK_POTION2_123 -> sendItemDialogue(player, Items.ATTACK_POTION2_123, "You've found an attack potion!")
|
||||
Items.STRENGTH_POTION2_117 -> sendItemDialogue(player, Items.STRENGTH_POTION2_117, "You've found a strength potion!")
|
||||
Items.DEFENCE_POTION2_135 -> sendItemDialogue(player, Items.DEFENCE_POTION2_135, "You've found a defence potion!")
|
||||
}
|
||||
setAttribute(player, MAZE_ATTRIBUTE_CHESTS_OPEN, getAttribute(player, MAZE_ATTRIBUTE_CHESTS_OPEN, 0))
|
||||
} else {
|
||||
sendMessage(player,"You find nothing of interest.")
|
||||
}
|
||||
return@on true
|
||||
}
|
||||
|
||||
on(Scenery.CHEST_3636, SCENERY, "search") { player, node ->
|
||||
sendMessage(player,"You find nothing of interest.")
|
||||
return@on true
|
||||
}
|
||||
|
||||
on(Scenery.WALL_3626, IntType.SCENERY, "open") { player, node ->
|
||||
sendMessage(player, "That bit doesn't open.") // 0xBrLo9woIY
|
||||
return@on true
|
||||
}
|
||||
|
||||
on(Scenery.WALL_3628, IntType.SCENERY, "open") { player, node ->
|
||||
// Door opening workaround
|
||||
// Ignore 3629(WALL_3629) and 3630(WALL_3630) in handleAutowalkDoor ignoreSecondDoor
|
||||
DoorActionHandler.handleAutowalkDoor(player, node as core.game.node.scenery.Scenery)
|
||||
return@on true
|
||||
}
|
||||
|
||||
on(Scenery.STRANGE_SHRINE_3634, IntType.SCENERY, "touch") { player, node ->
|
||||
player.unhook(this)
|
||||
closeOverlay(player)
|
||||
queueScript(player, 0, QueueStrength.SOFT) { stage: Int ->
|
||||
when (stage) {
|
||||
0 -> {
|
||||
lock(player, 6)
|
||||
sendGraphics(Graphics(86, 0, 3), player.location)
|
||||
animate(player,862)
|
||||
return@queueScript delayScript(player, 6)
|
||||
}
|
||||
1 -> {
|
||||
lock(player, 6)
|
||||
sendGraphics(Graphics(1576, 0, 0), player.location)
|
||||
animate(player,8939)
|
||||
playAudio(player, Sounds.TELEPORT_ALL_200)
|
||||
return@queueScript delayScript(player, 3)
|
||||
}
|
||||
2 -> {
|
||||
teleport(player, getAttribute(player, MAZE_ATTRIBUTE_RETURN_LOC, Location.create(3222, 3218, 0)))
|
||||
sendGraphics(Graphics(1577, 0, 0), player.location)
|
||||
removeAttribute(player, MAZE_ATTRIBUTE_RETURN_LOC)
|
||||
animate(player,8941)
|
||||
closeOverlay(player)
|
||||
return@queueScript delayScript(player, 1)
|
||||
}
|
||||
3 -> {
|
||||
calculateLoot(player)
|
||||
removeAttribute(player, MAZE_ATTRIBUTE_TICKS_LEFT)
|
||||
removeAttribute(player, MAZE_ATTRIBUTE_CHESTS_OPEN)
|
||||
return@queueScript stopExecuting(player)
|
||||
}
|
||||
else -> return@queueScript stopExecuting(player)
|
||||
}
|
||||
}
|
||||
|
||||
return@on true
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(entity: Entity, event: TickEvent) {
|
||||
if (entity is Player) {
|
||||
if (getAttribute(entity, MAZE_ATTRIBUTE_TICKS_LEFT, 0) > 0) {
|
||||
setAttribute(entity, MAZE_ATTRIBUTE_TICKS_LEFT, getAttribute(entity, MAZE_ATTRIBUTE_TICKS_LEFT, 0) - 1)
|
||||
}
|
||||
setVarp(entity, MAZE_TIMER_VARP, (getAttribute(entity, MAZE_ATTRIBUTE_TICKS_LEFT, 0) / 3), false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun defineAreaBorders(): Array<ZoneBorders> {
|
||||
return arrayOf(getRegionBorders(11591))
|
||||
}
|
||||
|
||||
override fun getRestrictions(): Array<ZoneRestriction> {
|
||||
return arrayOf(ZoneRestriction.RANDOM_EVENTS, ZoneRestriction.CANNON, ZoneRestriction.FOLLOWERS, ZoneRestriction.TELEPORT, ZoneRestriction.OFF_MAP)
|
||||
}
|
||||
|
||||
override fun areaEnter(entity: Entity) {
|
||||
if (entity is Player) {
|
||||
sendMessage(entity, "Head for the center of the maze.")
|
||||
entity.interfaceManager.removeTabs(0, 1, 2, 3, 4, 5, 6, 12)
|
||||
openOverlay(entity, MAZE_TIMER_INTERFACE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun areaLeave(entity: Entity, logout: Boolean) {
|
||||
if (entity is Player) {
|
||||
entity.interfaceManager.restoreTabs()
|
||||
closeOverlay(entity)
|
||||
entity.unhook(this)
|
||||
}
|
||||
}
|
||||
override fun entityStep(entity: Entity, location: Location, lastLocation: Location) {
|
||||
if (entity is Player) {
|
||||
entity.hook(Event.Tick, this)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package content.global.ame.events.maze
|
||||
|
||||
import content.global.ame.RandomEventNPC
|
||||
import core.api.*
|
||||
import core.api.utils.WeightBasedTable
|
||||
import core.game.interaction.QueueStrength
|
||||
import core.game.node.entity.npc.NPC
|
||||
import core.game.system.timer.impl.AntiMacro
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.build.DynamicRegion
|
||||
import core.game.world.update.flag.context.Graphics
|
||||
import org.rs09.consts.NPCs
|
||||
import org.rs09.consts.Sounds
|
||||
|
||||
class MazeNPC(var type: String = "", override var loot: WeightBasedTable? = null) : RandomEventNPC(NPCs.MYSTERIOUS_OLD_MAN_410) {
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
sendChat("Aha, you'll do ${player.username}!")
|
||||
face(player)
|
||||
queueScript(player, 4, QueueStrength.SOFT) { stage: Int ->
|
||||
when (stage) {
|
||||
0 -> {
|
||||
lock(player, 6)
|
||||
sendGraphics(Graphics(1576, 0, 0), player.location)
|
||||
animate(player,8939)
|
||||
playAudio(player, Sounds.TELEPORT_ALL_200)
|
||||
return@queueScript delayScript(player, 3)
|
||||
}
|
||||
1 -> {
|
||||
if (getAttribute<Location?>(player, MazeInterface.MAZE_ATTRIBUTE_RETURN_LOC, null) == null) {
|
||||
setAttribute(player, MazeInterface.MAZE_ATTRIBUTE_RETURN_LOC, player.location)
|
||||
}
|
||||
MazeInterface.initMaze(player)
|
||||
// Note: This event is NOT instanced:
|
||||
// Sources:
|
||||
// https://youtu.be/2gpzn9oNdy0 (2007)
|
||||
// https://youtu.be/Tni1HURgnxg (2008)
|
||||
// https://youtu.be/igdwDZOv9LU (2008)
|
||||
// https://youtu.be/0oBCkLArUmc (2011 - even with personal Mysterious Old Man) - "Sorry, this is not the old man you are looking for."
|
||||
// https://youtu.be/FMuKZm-Ikgs (2011)
|
||||
// val region = DynamicRegion.create(11591)
|
||||
teleport(player, MazeInterface.STARTING_POINTS.random()) // 10 random spots
|
||||
AntiMacro.terminateEventNpc(player)
|
||||
sendGraphics(Graphics(1577, 0, 0), player.location)
|
||||
animate(player,8941)
|
||||
removeAttribute(player, MazeInterface.MAZE_ATTRIBUTE_CHESTS_OPEN)
|
||||
return@queueScript stopExecuting(player)
|
||||
}
|
||||
else -> return@queueScript stopExecuting(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun talkTo(npc: NPC) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,9 @@ public final class DoorActionHandler {
|
||||
if (object.getCharge() == IN_USE_CHARGE) {
|
||||
return false;
|
||||
}
|
||||
final Scenery second = (object.getId() == 3) ? null : getSecondDoor(object, entity);
|
||||
// TODO: Maybe have this passed in as an optional parameter or overload handleAutowalkDoor?
|
||||
boolean ignoreSecondDoor = (object.getId() == 3628 || object.getId() == 3629 || object.getId() == 3630 || object.getId() == 3631|| object.getId() == 3632); // Ignore second door for Maze Random
|
||||
final Scenery second = (object.getId() == 3 || ignoreSecondDoor) ? null : getSecondDoor(object, entity);
|
||||
entity.lock(4);
|
||||
final Location loc = entity.getLocation();
|
||||
if (entity instanceof Player) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import core.game.node.entity.player.info.Rights
|
||||
import core.game.node.entity.player.info.login.LoginConfiguration
|
||||
import core.game.node.entity.player.link.SpellBookManager
|
||||
import core.game.node.entity.combat.spell.MagicSpell
|
||||
import content.global.ame.events.maze.MazeInterface
|
||||
import content.global.skill.summoning.familiar.FamiliarSpecial
|
||||
import core.game.node.item.GroundItemManager
|
||||
import core.game.node.item.Item
|
||||
@@ -670,6 +671,11 @@ object PacketProcessor {
|
||||
if (pkt.id == 6899)
|
||||
scenery = Scenery(6899, Location(3221, 9618))
|
||||
|
||||
// Random Event Maze chests are overridden by the walls, which needs to be hacked in.
|
||||
if ((scenery?.id == 3626 || scenery?.id == 3635) && (objId in 3635..3636)) {
|
||||
scenery = MazeInterface.overrideScenery(scenery, objId)
|
||||
}
|
||||
|
||||
// Family crest levers don't have varps associated with them, so their state is validated with attributes
|
||||
// instead, and they always appear as their down/odd variant in the server's map
|
||||
if (objId in 2421..2426 && objId % 2 == 0) {
|
||||
|
||||
Reference in New Issue
Block a user