From 1f0b8ed84a4c6b7e10b2a97fd2e4ce0dae26791d Mon Sep 17 00:00:00 2001 From: Spencer Carlson Date: Wed, 12 May 2021 17:10:53 -0700 Subject: [PATCH 1/7] Add: Stronghold of Security plugins: Door plugin that handles travelling through the stronghold doors Reward plugin that handles receiving rewards Quiz plugin that handles the quiz questions and answers Objects plugin that handles interaction with objects in the dungeon (such as portals, ladders, escape ropes) NPC keys for all NPCs in the Stronghold. NPC Spawns for all of the NPCs in the Stronghold. Travel Locations for the various levels of the dungeon. Pit of Pestilence map data from Revision #455 to the cache, and xteas folder. Music tracks for the Stronghold levels Modify: Change facepalm emote name to Slap Head Dialogue plugin to give dialogues the ability to wrap into new ones if the text is more than 4 lines. waitForPathing() method with optional boolean to ignore interaction distance requirement. Fix: Player's music player button state not loading correctly upon login --- cache/main_file_cache.dat2 | Bin 31268650 -> 31268650 bytes cache/main_file_cache.idx255 | Bin 78 -> 78 bytes cache/main_file_cache.idx5 | Bin 9324 -> 9324 bytes .../stronghold-of-security/boots.json | 8 + data/config/music/musicRegions.json | 8 +- .../dungeons/catacomb-of-famine.json | 1033 +++++++++++++++++ .../dungeons/sepulchre-of-death.json | 26 + .../dungeons/stronghold-of-security.json | 323 ++++++ .../npcs/dungeons/stronghold-of-security.json | 137 +++ data/config/stronghold-of-security-quiz.json5 | 390 +++++++ data/config/travel-locations-data.yaml | 16 + data/config/xteas/455.json | 15 + src/game-engine/config/index.ts | 13 + src/game-engine/util/strings.ts | 153 ++- src/game-engine/world/actor/actor.ts | 9 +- src/game-engine/world/actor/dialogue.ts | 118 +- src/game-engine/world/actor/player/player.ts | 1 - src/game-engine/world/config/animation-ids.ts | 5 + src/game-engine/world/config/object-ids.ts | 69 ++ .../world/config/static-positions.ts | 13 + .../stronghold-of-security-reward-data.ts | 22 + src/game-engine/world/direction.ts | 12 + src/plugins/buttons/player-emotes.plugin.ts | 6 +- .../stronghold-of-security-objects.plugin.ts | 284 +++++ .../stronghold-of-security-quiz.plugin.ts | 39 + .../stronghold-of-security-rewards.plugin.ts | 247 ++++ .../stronghold-of-security.plugin.ts | 223 ++++ .../player/login-update-settings.plugin.ts | 1 + 28 files changed, 3098 insertions(+), 73 deletions(-) create mode 100644 data/config/items/dungeons/stronghold-of-security/boots.json create mode 100644 data/config/npc-spawns/dungeons/catacomb-of-famine.json create mode 100644 data/config/npc-spawns/dungeons/sepulchre-of-death.json create mode 100644 data/config/npc-spawns/dungeons/stronghold-of-security.json create mode 100644 data/config/npcs/dungeons/stronghold-of-security.json create mode 100644 data/config/stronghold-of-security-quiz.json5 create mode 100644 data/config/xteas/455.json create mode 100644 src/game-engine/world/config/static-positions.ts create mode 100644 src/game-engine/world/config/stronghold-of-security-reward-data.ts create mode 100644 src/plugins/dungeons/stronghold-of-security/stronghold-of-security-objects.plugin.ts create mode 100644 src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts create mode 100644 src/plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin.ts create mode 100644 src/plugins/dungeons/stronghold-of-security/stronghold-of-security.plugin.ts diff --git a/cache/main_file_cache.dat2 b/cache/main_file_cache.dat2 index 027e2440b558e87cc92ea589e1e4f00800906360..1d378bd2de61aae8931ede8c72a603fb39cd664f 100644 GIT binary patch delta 18070 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 delta 18058 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 Vf_^Xn2Eh;*CR#Ihj}Rrp{{uz)D@p(W diff --git a/cache/main_file_cache.idx255 b/cache/main_file_cache.idx255 index 762669eddb80cef480f6f949802abc6d12ddd799..651e739f5c3f53a2ac71a7f0b0fab9a83425b370 100644 GIT binary patch delta 9 QcmebCo1nnxH&Ia+01ZY0djJ3c delta 9 QcmebCo1nmGGEq?%01Vy&VE_OC diff --git a/cache/main_file_cache.idx5 b/cache/main_file_cache.idx5 index fe159062d93715fd4a7c6eaf88ec965d795c09a9..b5ea860a85be3b54b2549e7d63c02364c0d4e393 100644 GIT binary patch delta 14 VcmaFk@y26=ix8vGW>+C~ApkBm1jPUV delta 14 VcmaFk@y26=ix8vmW>+C~ApkBs1jYaW diff --git a/data/config/items/dungeons/stronghold-of-security/boots.json b/data/config/items/dungeons/stronghold-of-security/boots.json new file mode 100644 index 000000000..e8c128b86 --- /dev/null +++ b/data/config/items/dungeons/stronghold-of-security/boots.json @@ -0,0 +1,8 @@ +{ + "rs:Fancy boots": { + "game_id": 9005 + }, + "rs:Fighting boots": { + "game_id": 9006 + } +} diff --git a/data/config/music/musicRegions.json b/data/config/music/musicRegions.json index aab659f8f..29fca08ea 100644 --- a/data/config/music/musicRegions.json +++ b/data/config/music/musicRegions.json @@ -603,7 +603,7 @@ "songName": "Dance of Death", "musicTabButtonId": 436, "regionIds": [ - 0 + 9297 ] }, { @@ -762,7 +762,7 @@ "songName": "Dogs of War", "musicTabButtonId": 437, "regionIds": [ - 0 + 7505 ] }, { @@ -1076,7 +1076,7 @@ "songName": "Food For Thought", "musicTabButtonId": 438, "regionIds": [ - 0 + 8017 ] }, { @@ -1824,7 +1824,7 @@ "songName": "Malady", "musicTabButtonId": 439, "regionIds": [ - 0 + 8530 ] }, { diff --git a/data/config/npc-spawns/dungeons/catacomb-of-famine.json b/data/config/npc-spawns/dungeons/catacomb-of-famine.json new file mode 100644 index 000000000..5e6084718 --- /dev/null +++ b/data/config/npc-spawns/dungeons/catacomb-of-famine.json @@ -0,0 +1,1033 @@ +[ + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 1988, + "spawn_y": 5235 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 1989, + "spawn_y": 5239 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 1991, + "spawn_y": 5242 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 1992, + "spawn_y": 5235 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 1994, + "spawn_y": 5239 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 1994, + "spawn_y": 5241 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 2003, + "spawn_y": 5202 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 2003, + "spawn_y": 5205 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 2005, + "spawn_y": 5199 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 2006, + "spawn_y": 5202 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 2006, + "spawn_y": 5205 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 2008, + "spawn_y": 5201 + }, + { + "npc": "rs:Flesh Crawler_2", + "movement_radius": 6, + "face": "WEST", + "id": 2500, + "spawn_level": 0, + "spawn_x": 2008, + "spawn_y": 5204 + }, + { + "npc": "rs:Flesh Crawler_1", + "spawn_x": 2037, + "spawn_y": 5193, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler_1", + "spawn_x": 2038, + "spawn_y": 5187, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler_1", + "spawn_x": 2040, + "spawn_y": 5186, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler_1", + "spawn_x": 2041, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler_1", + "spawn_x": 2042, + "spawn_y": 5192, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler_1", + "spawn_x": 2043, + "spawn_y": 5186, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler_1", + "spawn_x": 2044, + "spawn_y": 5189, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler_1", + "spawn_x": 2045, + "spawn_y": 5193, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler", + "spawn_x": 2013, + "spawn_y": 5237, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler", + "spawn_x": 2021, + "spawn_y": 5237, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler", + "spawn_x": 2032, + "spawn_y": 5231, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler", + "spawn_x": 2040, + "spawn_y": 5231, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Flesh Crawler", + "spawn_x": 2045, + "spawn_y": 5233, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Giant rat_7", + "spawn_x": 1988, + "spawn_y": 5189, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Giant rat_7", + "spawn_x": 1989, + "spawn_y": 5192, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + + { + "npc": "rs:Giant rat_7", + "spawn_x": 2015, + "spawn_y": 5236, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + + { + "npc": "rs:Giant rat_7", + "spawn_x": 2037, + "spawn_y": 5211, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Giant rat_7", + "spawn_x": 1990, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Giant rat_7", + "spawn_x": 1992, + "spawn_y": 5188, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Giant rat_7", + "spawn_x": 2019, + "spawn_y": 5235, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Giant rat_7", + "spawn_x": 2040, + "spawn_y": 5215, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Giant rat_7", + "spawn_x": 1995, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Giant rat_7", + "spawn_x": 2019, + "spawn_y": 5238, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1987, + "spawn_y": 5202, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1988, + "spawn_y": 5200, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1988, + "spawn_y": 5205, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1989, + "spawn_y": 5223, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1989, + "spawn_y": 5226, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1989, + "spawn_y": 5236, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1990, + "spawn_y": 5228, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1990, + "spawn_y": 5237, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1991, + "spawn_y": 5233, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1992, + "spawn_y": 5200, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1992, + "spawn_y": 5242, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1993, + "spawn_y": 5191, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1993, + "spawn_y": 5199, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1993, + "spawn_y": 5235, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1994, + "spawn_y": 5207, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1994, + "spawn_y": 5211, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1994, + "spawn_y": 5218, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1995, + "spawn_y": 5214, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 1995, + "spawn_y": 5239, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2000, + "spawn_y": 5187, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2004, + "spawn_y": 5186, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2004, + "spawn_y": 5189, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2004, + "spawn_y": 5204, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2004, + "spawn_y": 5207, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2005, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2007, + "spawn_y": 5203, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2009, + "spawn_y": 5187, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2012, + "spawn_y": 5194, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2013, + "spawn_y": 5236, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2014, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2014, + "spawn_y": 5192, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2015, + "spawn_y": 5186, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2017, + "spawn_y": 5192, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2018, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2018, + "spawn_y": 5238, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2022, + "spawn_y": 5188, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2024, + "spawn_y": 5193, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2027, + "spawn_y": 5188, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2027, + "spawn_y": 5192, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2028, + "spawn_y": 5232, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2029, + "spawn_y": 5185, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2030, + "spawn_y": 5235, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2030, + "spawn_y": 5244, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2031, + "spawn_y": 5191, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2031, + "spawn_y": 5243, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2032, + "spawn_y": 5230, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2032, + "spawn_y": 5242, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2033, + "spawn_y": 5242, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2033, + "spawn_y": 5246, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2034, + "spawn_y": 5234, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2034, + "spawn_y": 5244, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2036, + "spawn_y": 5202, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2038, + "spawn_y": 5207, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2040, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2041, + "spawn_y": 5186, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2042, + "spawn_y": 5200, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2042, + "spawn_y": 5204, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2044, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2044, + "spawn_y": 5193, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Rat_16", + "spawn_x": 2046, + "spawn_y": 5201, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_18", + "spawn_x": 2029, + "spawn_y": 5236, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_18", + "spawn_x": 2031, + "spawn_y": 5230, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_18", + "spawn_x": 2031, + "spawn_y": 5235, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_18", + "spawn_x": 2026, + "spawn_y": 5234, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_18", + "spawn_x": 2031, + "spawn_y": 5233, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 1988, + "spawn_y": 5192, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 1990, + "spawn_y": 5188, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 1993, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2003, + "spawn_y": 5200, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2005, + "spawn_y": 5201, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2007, + "spawn_y": 5209, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2039, + "spawn_y": 5212, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2039, + "spawn_y": 5215, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2039, + "spawn_y": 5218, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2040, + "spawn_y": 5213, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2041, + "spawn_y": 5218, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2042, + "spawn_y": 5213, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2042, + "spawn_y": 5215, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2043, + "spawn_y": 5215, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_19", + "spawn_x": 2043, + "spawn_y": 5217, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 1988, + "spawn_y": 5242, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 1990, + "spawn_y": 5234, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 1992, + "spawn_y": 5238, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 2008, + "spawn_y": 5190, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 2014, + "spawn_y": 5187, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 2023, + "spawn_y": 5193, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 2025, + "spawn_y": 5189, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 1988, + "spawn_y": 5237, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 2018, + "spawn_y": 5186, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 2027, + "spawn_y": 5186, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + }, + { + "npc": "rs:Zombie_20", + "spawn_x": 2029, + "spawn_y": 5191, + "spawn_level": 0, + "face": "WEST", + "movement_radius": 6 + } +] diff --git a/data/config/npc-spawns/dungeons/sepulchre-of-death.json b/data/config/npc-spawns/dungeons/sepulchre-of-death.json new file mode 100644 index 000000000..ca5f1efce --- /dev/null +++ b/data/config/npc-spawns/dungeons/sepulchre-of-death.json @@ -0,0 +1,26 @@ +[ + {"npc":"rs:Shade","spawn_level":0,"spawn_x":2356,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade","spawn_level":0,"spawn_x":2357,"spawn_y":5213,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade","spawn_level":0,"spawn_x":2363,"spawn_y":5212,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade","spawn_level":0,"spawn_x":2365,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_1","spawn_level":0,"spawn_x":2356,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_1","spawn_level":0,"spawn_x":2357,"spawn_y":5213,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_1","spawn_level":0,"spawn_x":2363,"spawn_y":5212,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_1","spawn_level":0,"spawn_x":2365,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_2","spawn_level":0,"spawn_x":2356,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_2","spawn_level":0,"spawn_x":2357,"spawn_y":5213,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_2","spawn_level":0,"spawn_x":2363,"spawn_y":5212,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_2","spawn_level":0,"spawn_x":2365,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_3","spawn_level":0,"spawn_x":2356,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_3","spawn_level":0,"spawn_x":2357,"spawn_y":5213,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_3","spawn_level":0,"spawn_x":2363,"spawn_y":5212,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_3","spawn_level":0,"spawn_x":2365,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_4","spawn_level":0,"spawn_x":2356,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_4","spawn_level":0,"spawn_x":2357,"spawn_y":5213,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_4","spawn_level":0,"spawn_x":2363,"spawn_y":5212,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_4","spawn_level":0,"spawn_x":2365,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_5","spawn_level":0,"spawn_x":2356,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_5","spawn_level":0,"spawn_x":2357,"spawn_y":5213,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_5","spawn_level":0,"spawn_x":2363,"spawn_y":5212,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Shade_5","spawn_level":0,"spawn_x":2365,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +] diff --git a/data/config/npc-spawns/dungeons/stronghold-of-security.json b/data/config/npc-spawns/dungeons/stronghold-of-security.json new file mode 100644 index 000000000..21227a7e6 --- /dev/null +++ b/data/config/npc-spawns/dungeons/stronghold-of-security.json @@ -0,0 +1,323 @@ +[ + {"npc":"rs:Ankou","spawn_level":0,"spawn_x":2355,"spawn_y":5241,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2357,"spawn_y":5240,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2358,"spawn_y":5243,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2358,"spawn_y":5245,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2359,"spawn_y":5239,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2359,"spawn_y":5241,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2361,"spawn_y":5240,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2361,"spawn_y":5242,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2363,"spawn_y":5240,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2324,"spawn_y":5198,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2325,"spawn_y":5197,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2326,"spawn_y":5201,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2326,"spawn_y":5205,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2326,"spawn_y":5206,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2327,"spawn_y":5199,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2327,"spawn_y":5202,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2328,"spawn_y":5197,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2329,"spawn_y":5203,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2330,"spawn_y":5195,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2331,"spawn_y":5202,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2315,"spawn_y":5229,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2317,"spawn_y":5226,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2318,"spawn_y":5230,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2320,"spawn_y":5224,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2321,"spawn_y":5232,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2322,"spawn_y":5222,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2322,"spawn_y":5227,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2322,"spawn_y":5229,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2323,"spawn_y":5224,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2324,"spawn_y":5230,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2324,"spawn_y":5236,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2327,"spawn_y":5227,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2326,"spawn_y":5199,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2326,"spawn_y":5208,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2332,"spawn_y":5202,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2350,"spawn_y":5200,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2352,"spawn_y":5195,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2353,"spawn_y":5192,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2355,"spawn_y":5198,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2328,"spawn_y":5205,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2329,"spawn_y":5200,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2348,"spawn_y":5197,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2353,"spawn_y":5203,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2354,"spawn_y":5191,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_18","spawn_level":0,"spawn_x":2311,"spawn_y":5187,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Skeleton_18","spawn_level":0,"spawn_x":2311,"spawn_y":5193,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2352,"spawn_y":5189,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2353,"spawn_y":5197,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2356,"spawn_y":5204,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2357,"spawn_y":5194,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2305,"spawn_y":5233,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2305,"spawn_y":5241,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2306,"spawn_y":5229,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2306,"spawn_y":5243,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2309,"spawn_y":5246,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2311,"spawn_y":5237,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2314,"spawn_y":5237,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2314,"spawn_y":5244,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2315,"spawn_y":5236,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2316,"spawn_y":5227,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2322,"spawn_y":5235,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2325,"spawn_y":5228,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1859,"spawn_y":5193,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1859,"spawn_y":5203,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5192,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5200,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5229,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5231,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1862,"spawn_y":5188,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1862,"spawn_y":5204,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1863,"spawn_y":5227,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1864,"spawn_y":5202,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1865,"spawn_y":5188,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1866,"spawn_y":5204,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1868,"spawn_y":5189,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1869,"spawn_y":5226,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1870,"spawn_y":5192,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1871,"spawn_y":5232,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1872,"spawn_y":5190,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1872,"spawn_y":5218,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1872,"spawn_y":5239,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1873,"spawn_y":5188,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1874,"spawn_y":5200,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1874,"spawn_y":5210,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1876,"spawn_y":5189,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1876,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1877,"spawn_y":5220,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1879,"spawn_y":5198,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1879,"spawn_y":5230,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1879,"spawn_y":5234,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1880,"spawn_y":5200,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1880,"spawn_y":5239,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1880,"spawn_y":5243,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1881,"spawn_y":5232,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1881,"spawn_y":5241,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1882,"spawn_y":5199,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1882,"spawn_y":5201,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1882,"spawn_y":5243,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1883,"spawn_y":5206,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1883,"spawn_y":5234,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1884,"spawn_y":5230,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1884,"spawn_y":5232,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1885,"spawn_y":5205,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1886,"spawn_y":5231,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1887,"spawn_y":5203,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1889,"spawn_y":5206,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1890,"spawn_y":5242,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1895,"spawn_y":5242,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1898,"spawn_y":5241,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1900,"spawn_y":5240,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1900,"spawn_y":5244,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1904,"spawn_y":5235,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1908,"spawn_y":5203,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1909,"spawn_y":5243,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1910,"spawn_y":5202,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1911,"spawn_y":5205,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1912,"spawn_y":5238,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1913,"spawn_y":5235,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2142,"spawn_y":5252,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2145,"spawn_y":5252,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2145,"spawn_y":5256,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2148,"spawn_y":5253,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2149,"spawn_y":5255,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2152,"spawn_y":5251,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2153,"spawn_y":5254,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2155,"spawn_y":5252,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2158,"spawn_y":5282,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2161,"spawn_y":5281,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2162,"spawn_y":5284,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2164,"spawn_y":5280,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2119,"spawn_y":5296,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2120,"spawn_y":5292,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2120,"spawn_y":5299,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2122,"spawn_y":5291,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2123,"spawn_y":5302,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2127,"spawn_y":5305,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2129,"spawn_y":5302,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2130,"spawn_y":5298,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2163,"spawn_y":5301,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2166,"spawn_y":5303,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2166,"spawn_y":5306,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2167,"spawn_y":5300,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2169,"spawn_y":5303,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2170,"spawn_y":5306,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2120,"spawn_y":5275,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2121,"spawn_y":5273,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2123,"spawn_y":5270,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2123,"spawn_y":5275,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2124,"spawn_y":5272,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2124,"spawn_y":5274,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2125,"spawn_y":5270,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2126,"spawn_y":5274,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2127,"spawn_y":5272,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2129,"spawn_y":5268,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2129,"spawn_y":5270,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2130,"spawn_y":5272,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2131,"spawn_y":5267,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2142,"spawn_y":5256,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2143,"spawn_y":5251,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2145,"spawn_y":5253,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2145,"spawn_y":5306,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2146,"spawn_y":5259,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2146,"spawn_y":5306,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2147,"spawn_y":5255,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2147,"spawn_y":5305,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2147,"spawn_y":5307,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2149,"spawn_y":5304,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2149,"spawn_y":5305,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2149,"spawn_y":5306,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2150,"spawn_y":5307,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2150,"spawn_y":5308,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2151,"spawn_y":5268,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2151,"spawn_y":5305,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2151,"spawn_y":5308,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2152,"spawn_y":5268,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2152,"spawn_y":5306,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5270,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5305,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5306,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5307,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5269,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5271,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5273,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5305,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2155,"spawn_y":5254,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2155,"spawn_y":5305,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2157,"spawn_y":5271,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2157,"spawn_y":5273,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2157,"spawn_y":5274,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2119,"spawn_y":5274,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2120,"spawn_y":5277,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2121,"spawn_y":5276,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2122,"spawn_y":5271,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2124,"spawn_y":5269,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2124,"spawn_y":5273,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2126,"spawn_y":5270,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2126,"spawn_y":5272,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2127,"spawn_y":5269,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2127,"spawn_y":5273,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2128,"spawn_y":5271,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2129,"spawn_y":5267,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2129,"spawn_y":5273,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2150,"spawn_y":5267,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2152,"spawn_y":5270,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2155,"spawn_y":5270,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2155,"spawn_y":5271,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2156,"spawn_y":5273,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2156,"spawn_y":5274,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2165,"spawn_y":5255,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2166,"spawn_y":5256,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2167,"spawn_y":5251,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2167,"spawn_y":5253,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2167,"spawn_y":5255,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2168,"spawn_y":5253,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2168,"spawn_y":5256,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2168,"spawn_y":5257,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2169,"spawn_y":5250,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2169,"spawn_y":5254,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2169,"spawn_y":5255,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2170,"spawn_y":5252,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2170,"spawn_y":5254,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2170,"spawn_y":5256,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2171,"spawn_y":5253,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2172,"spawn_y":5254,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2169,"spawn_y":5290,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2170,"spawn_y":5285,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2171,"spawn_y":5280,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2172,"spawn_y":5274,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2170,"spawn_y":5277,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2170,"spawn_y":5288,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2172,"spawn_y":5283,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2173,"spawn_y":5272,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1862,"spawn_y":5190,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1863,"spawn_y":5189,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1867,"spawn_y":5188,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1870,"spawn_y":5235,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1871,"spawn_y":5191,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1871,"spawn_y":5242,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1872,"spawn_y":5227,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1873,"spawn_y":5212,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1873,"spawn_y":5238,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1874,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1875,"spawn_y":5214,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1875,"spawn_y":5218,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1878,"spawn_y":5216,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1879,"spawn_y":5218,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1880,"spawn_y":5217,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1880,"spawn_y":5220,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1890,"spawn_y":5195,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1890,"spawn_y":5243,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5189,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5198,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5238,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5241,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1894,"spawn_y":5243,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1897,"spawn_y":5196,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1897,"spawn_y":5244,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1899,"spawn_y":5198,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1899,"spawn_y":5242,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1900,"spawn_y":5208,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1900,"spawn_y":5210,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5191,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5197,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5201,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5206,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5193,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5199,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5204,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5207,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5242,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1904,"spawn_y":5191,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1860,"spawn_y":5205,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1860,"spawn_y":5222,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1860,"spawn_y":5226,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1862,"spawn_y":5201,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1859,"spawn_y":5201,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1860,"spawn_y":5215,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1862,"spawn_y":5220,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1863,"spawn_y":5215,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1863,"spawn_y":5217,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1864,"spawn_y":5218,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1859,"spawn_y":5219,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1861,"spawn_y":5224,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1862,"spawn_y":5228,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1864,"spawn_y":5204,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1884,"spawn_y":5207,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1885,"spawn_y":5203,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1891,"spawn_y":5236,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1894,"spawn_y":5229,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1910,"spawn_y":5236,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1913,"spawn_y":5239,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1876,"spawn_y":5198,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1884,"spawn_y":5201,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1887,"spawn_y":5205,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1893,"spawn_y":5235,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1895,"spawn_y":5228,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1896,"spawn_y":5232,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1911,"spawn_y":5240,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1912,"spawn_y":5244,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1913,"spawn_y":5236,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1877,"spawn_y":5199,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1892,"spawn_y":5226,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1894,"spawn_y":5233,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1905,"spawn_y":5235,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1912,"spawn_y":5242,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1870,"spawn_y":5226,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1871,"spawn_y":5229,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1871,"spawn_y":5236,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1872,"spawn_y":5233,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1873,"spawn_y":5239,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1886,"spawn_y":5188,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1887,"spawn_y":5221,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5187,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5191,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5214,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5217,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1890,"spawn_y":5224,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1892,"spawn_y":5217,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1892,"spawn_y":5220,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1894,"spawn_y":5193,"movement_radius":6,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1897,"spawn_y":5189,"movement_radius":6,"face":"WEST"} +] \ No newline at end of file diff --git a/data/config/npcs/dungeons/stronghold-of-security.json b/data/config/npcs/dungeons/stronghold-of-security.json new file mode 100644 index 000000000..5ff8fc630 --- /dev/null +++ b/data/config/npcs/dungeons/stronghold-of-security.json @@ -0,0 +1,137 @@ +{ + "rs:Gate of War": { + "game_id": 4377 + }, + "rs:Ricketty door": { + "game_id": 4378 + }, + "rs:Oozing barrier": { + "game_id": 4379 + }, + "rs:Portal of Death": { + "game_id": 4380 + }, + "rs:Ankou": { + "game_id": 4381 + }, + "rs:Ankou_1": { + "game_id": 4382 + }, + "rs:Ankou_2": { + "game_id": 4383 + }, + "rs:Skeleton_16": { + "game_id": 4384 + }, + "rs:Skeleton_17": { + "game_id": 4385 + }, + "rs:Skeleton_18": { + "game_id": 4386 + }, + "rs:Ghost_8": { + "game_id": 4387 + }, + "rs:Ghost_9": { + "game_id": 4388 + }, + "rs:Flesh Crawler": { + "game_id": 4389 + }, + "rs:Flesh Crawler_1": { + "game_id": 4390 + }, + "rs:Flesh Crawler_2": { + "game_id": 4391 + }, + "rs:Zombie_18": { + "game_id": 4392 + }, + "rs:Zombie_19": { + "game_id": 4393 + }, + "rs:Zombie_20": { + "game_id": 4394 + }, + "rs:Giant rat_7": { + "game_id": 4395 + }, + "rs:Rat_16": { + "game_id": 4396 + }, + "rs:Catablepon": { + "game_id": 4397 + }, + "rs:Catablepon_1": { + "game_id": 4398 + }, + "rs:Catablepon_2": { + "game_id": 4399 + }, + "rs:Giant spider_2": { + "game_id": 4400 + }, + "rs:Spider_5": { + "game_id": 4401 + }, + "rs:Scorpion_2": { + "game_id": 4402 + }, + "rs:Scorpion_3": { + "game_id": 4403 + }, + "rs:Minotaur": { + "game_id": 4404 + }, + "rs:Minotaur_1": { + "game_id": 4405 + }, + "rs:Minotaur_2": { + "game_id": 4406 + }, + "rs:Goblin_47": { + "game_id": 4407 + }, + "rs:Goblin_48": { + "game_id": 4408 + }, + "rs:Goblin_49": { + "game_id": 4409 + }, + "rs:Goblin_50": { + "game_id": 4410 + }, + "rs:Goblin_51": { + "game_id": 4411 + }, + "rs:Goblin_52": { + "game_id": 4412 + }, + "rs:Wolf_3": { + "game_id": 4413 + }, + "rs:Wolf_4": { + "game_id": 4414 + }, + "rs:Rat_17": { + "game_id": 4415 + }, + "rs:Shade": { + "game_id": 425 + }, + "rs:Shade_1": { + "game_id": 426 + }, + "rs:Shade_2": { + "game_id": 427 + }, + "rs:Shade_3": { + "game_id": 428 + }, + "rs:Shade_4": { + "game_id": 429 + }, + "rs:Shade_5": { + "game_id": 430 + } +} diff --git a/data/config/stronghold-of-security-quiz.json5 b/data/config/stronghold-of-security-quiz.json5 new file mode 100644 index 000000000..1fc746735 --- /dev/null +++ b/data/config/stronghold-of-security-quiz.json5 @@ -0,0 +1,390 @@ +{ + prefix: "To pass you must answer me this: ", + questions: [ + { + questionText: "How often should you change your recovery questions?", + options: [ + { + optionText: "Never", + passable: false, + doorResponse: "Correct! This is the ideal, every few months change your questions, but make sure you can remember the answers! Don't use personal details for your recoveries." + }, + { + optionText: "Every day", + passable: false, + doorResponse: "Normally recovery questions will take 14 days to become active, so there's no point in changing them everyday! Don't use personal details for your recoveries." + }, + { + optionText: "Every couple of months", + passable: true, + doorResponse: "Correct! This is the ideal, every few months change your questions, but make sure you can remember the answers! Don't use personal details for your recoveries." + } + ] + }, + { + questionText: "What do I do if a moderator asks me for my account details?", + options: [ + { + optionText: "Tell them whatever they want to know.", + passable: false, + doorResponse: "Wrong! Never give your account details to anyone! This includes things like account creation details, contact details and passwords. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Politely tell them no.", + passable: true, + doorResponse: "Ok! Don't tell them the details. But reporting the incident to Jagex would help. Use the Report Abuse button. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Politely tell them no then use the report abuse button.", + passable: true, + doorResponse: "Correct! Report any attempt to gain your account details as it is a very serious breach of RuneScape's rules. Never use personal details for security answers or bank PINs!" + } + ] + }, + { + questionText: "Who can I give my password to?", + options: [ + { + optionText: "My friends.", + passable: false, + doorResponse: "Wrong! Your password should be kept secret from everyone. You should *never* give it out under any circumstances." + }, + { + optionText: "My brother or sister.", + passable: false, + doorResponse: "Wrong! Your password should be kept secret from everyone. You should *never* give it out under any circumstances." + }, + { + optionText: "Nobody.", + passable: true, + doorResponse: "Correct! Your password should be kept secret from everyone. You should *never* give it out under any circumstances." + } + ] + }, + { + questionText: "How do I set a bank PIN?", + options: [ + { + optionText: "Use the account management section on the website.", + passable: false, + doorResponse: "Wrong! Your password can be changed from the account management section, but you must talk to a banker to set a bank PIN. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Talk to any banker.", + passable: true, + doorResponse: "Correct! Simply talking to a banker will give you the option to set a bank PIN. Never use personal details for passwords or bank PINs!" + } + ] + }, + { + questionText: "Who is it ok to share my account with?", + options: [ + { + optionText: "My friends.", + passable: false, + doorResponse: "Wrong! Your account may only be used by you." + }, + { + optionText: "My relatives.", + passable: false, + doorResponse: "Wrong! Your account may only be used by you." + }, + { + optionText: "Nobody.", + passable: true, + doorResponse: "Correct! Your account may only be used by you." + } + ] + }, + { + questionText: "My friend asks me for my password so that he can do a difficult quest for me. Do I give it to him?", + options: [ + { + optionText: "Yes. He is my best friend and I already spent ages trying this quest.", + passable: false, + doorResponse: "Wrong! Don't give your password to anyone otherwise you can lose everything you have worked so hard for." + }, + { + optionText: "Don't give him my passwoord.", + passable: true, + doorResponse: "Correct! You can make it alone and the success will taste even better. Don't forget you can ask people for advice too!" + }, + { + optionText: "Let him do the quest but in the same room the whole time.", + passable: false, + doorResponse: "Wrong! Never let anyone use your account for any reason. It only takes a few seconds to change your password." + } + ] + }, + { + questionText: "Will Jagex block me from saying my PIN in game?", + options: [ + { + optionText: "Yes.", + passable: false, + doorResponse: "Wrong! Jagex does NOT block your PIN so don't type it! Never use personal details for recoveries or bank PINs!" + }, + { + optionText: "No.", + passable: true, + doorResponse: "Correct! Jagex will not block your PIN so don't type it! Never use personal details for recoveries or bank PINs!" + } + ] + }, + { + questionText: "What do I do if I think I have a keylogger or a virus?", + options: [ + { + optionText: "Virus scan my computer then change my password and recoveries.", + passable: true, + doorResponse: "Correct! Removing the keylogger must be the priority, otherwise anything you type can be given away. Remember to change your password and bank PIN afterwards." + }, + { + optionText: "Change my password then virus scan my computer.", + passable: false, + doorResponse: "Wrong! If you change your password while you still have the keylogger, it will still be insecure. Remove the keylogger first. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Nothing. It will go away on its own.", + passable: false, + doorResponse: "Wrong! This could mean your account may be accessed by someone else. Remove the keylogger then change your password. Never use personal details for security answers or bank PINs!" + } + ] + }, + { + questionText: "A website says I can become a player moderator by giving them my password, what do I do?", + options: [ + { + optionText: "Nothing.", + passable: false, + doorResponse: "Quite good. But we should try to stop scammers. So please reoport any attempt to gain your account details as it is a very serious breach of RuneScape's rules. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Give them my password.", + passable: false, + doorResponse: "Wrong! This will almost certainly lead to your account being hijacked. No website can make you a moderator as they are hand picked by Jagex." + }, + { + optionText: "Don't tell them anything and imform Jagex through the game website.", + passable: true, + doorResponse: "Correct! By informing us we can have the site taken down so other people will not have their accounts hijacked by this scam. Remember that moderators are hand picked by Jagex." + } + ] + }, + { + questionText: "What do you do if someone asks you for your password or recoveries to make you a member for free?", + options: [ + { + optionText: "Give them the information they asked for.", + passable: false, + doorResponse: "Wrong! Never give your account details to anyone! This includes things like account creation details, contact details and passwords. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Don't tell them anything and ignore them.", + passable: true, + doorResponse: "Quite good. But we should try to stop scammers. So please report them using the 'Report Abuse' button." + }, + { + optionText: "Don't tell them anything and click the 'Report Abuse' button.", + passable: true, + doorResponse: "Correct! Press the 'Report Abuse' button and fill in the offending player's name and the correct category." + } + ] + }, + { + questionText: "Where should I enter my RuneScape password?", + options: [ + { + optionText: "On RuneScape and all fansites.", + passable: false, + doorResponse: "Wrong! Always use a unique password purely for your RuneScape account." + }, + { + optionText: "Only on the RuneScape website.", + passable: true, + doorResponse: "Correct! Always make sure you are entering password only on the RuneScape website as other sites may try to steal it." + }, + { + optionText: "On all websites I visit.", + passable: false, + doorResponse: "Wrong! This is very insecure and will may lead to your account being stolen.!" + } + ] + }, + { + questionText: "Where can I find cheats for RuneScape?", + options: [ + { + optionText: "On the RuneScape website.", + passable: false, + doorResponse: "Wrong! There are NO RuneScape cheats coded into the game. Any sites claiming to have cheats are fakes and may lead to your account being stolen if you given them your password." + }, + { + optionText: "By searching the internet.", + passable: false, + doorResponse: "Wrong! There are NO RuneScape cheats coded into the game. Any sites claiming to have cheats are fakes and may lead to your account being stolen if you given them your password." + }, + { + optionText: "Nowhere.", + passable: true, + doorResponse: "Correct! There are NO RuneScape cheats coded into the game. Any sites claiming to have cheats are fakes and may lead to your account being stolen if you given them your password." + } + ] + }, + { + questionText: "What do you do if someone asks you for your password or recoveries to make you a player moderator?", + options: [ + { + optionText: "Don't give them the information and send a 'Abuse report'.", + passable: true, + doorResponse: "Correct! Use the 'Report Abuse' button and fill in the offending player's name and the correct category." + }, + { + optionText: "Don't tell them anything and ignore them.", + passable: true, + doorResponse: "Quite good. But we should try to stop scammers. So please report them using the 'Report Abuse' button." + }, + { + optionText: "Give them the information they asked for.", + passable: false, + doorResponse: "Wrong! Jagex never ask for your account information - especially to become a player moderator. Press the 'Report Abuse' button and fill in the offending player's name and the correct category." + } + ] + }, + { + questionText: "Why do I need to type in recovery questions?", + options: [ + { + optionText: "To help me recover my password if I forget it or it is stolen.", + passable: true, + doorResponse: "Correct! Your recovery questions will help Jagex staff protect and return your account if it is stolen. Never use personal details for recoveries or bank PINs!" + }, + { + optionText: "To let Jagex know more about its players.", + passable: false, + doorResponse: "INCORRECTRESPONSE" + }, + { + optionText: "To see if I can type in random letters on my keyboard.", + passable: false, + doorResponse: "INCORRECTRESPONSE" + } + ] + }, + { + questionText: "What should I do if I think someone knows my recovery answers?", + options: [ + { + optionText: "Tell them never to use them.", + passable: false, + doorResponse: "INCORRECTRESPONSE" + }, + { + optionText: "Use the Account Management section on the RuneScape website.", + passable: false, + doorResponse: "Wrong! If you use the Account Management section to change your recovery questions, it will take 14 days to come into effect, someone may have access to your account in this time." + }, + { + optionText: "Use the 'Recover a Lost Password' section on the RuneScape website.", + passable: false, + doorResponse: "INCORRECTRESPONSE" + } + ] + }, + { + questionText: "Can I leave my account logged in while I'm out of the room?", + options: [ + { + optionText: "If I'm going to be quick.", + passable: false, + doorResponse: "Wrong! You should logout in case you are attacked or receive a random event. Leaving your character logged in can also allow someone to steal your items or entire account!" + }, + { + optionText: "Yes.", + passable: false, + doorResponse: "Wrong! You should logout in case you are attacked or receive a random event. Leaving your character logged in can also allow someone to steal your items or entire account!" + }, + { + optionText: "No.", + passable: true, + doorResponse: "Correct! This is the safest, both in terms of security and keeping your items! Leaving your character logged in can also allow someone to steal your items or entire account!" + } + ] + }, + { + questionText: "Does Jagex really hide your password if you accidentally say it in game?", + options: [ + { + optionText: "Yes - Jagex blocks your password.", + passable: false, + doorResponse: "Wrong! Jagex does NOT block your password so don't type it! Never use personal details for passwords or bank PINs!" + }, + { + optionText: "No - Jagex does not block your password.", + passable: true, + doorResponse: "Correct! We do not block your password. Do not say it in game as someone may steal your account!" + } + ] + }, + { + questionText: "My friend uses this great add-on program he got from a website, should I?", + options: [ + { + optionText: "No, it might steal my password.", + passable: true, + doorResponse: "Correct! The only safe add-on for RuneScape is the Windows client available from our RuneScape website." + }, + { + optionText: "I'll give it a try and see if I like it.", + passable: false, + doorResponse: "Wrong! The program may steal your password and is against the rules to use." + }, + { + optionText: "Sure, he's used it a lot, so can I.", + passable: false, + doorResponse: "Wrong! The program may steal your password and is against the rules to use." + } + ] + }, + { + questionText: "What do you do if someone tells you that you have won the RuneScape Lottery and asks you for your password or recoveries to award your prize?", + options: [ + { + optionText: "Give them the information they asked for.", + passable: false, + doorResponse: "Wrong! This will almost certainly lead to your account being hijacked. [No website can make you a moderator as they are hand picked by Jagex.]" + }, + { + optionText: "Don't tell them anything and ignore them.", + passable: true, + doorResponse: "Quite good. But we should try to stop scammers. So please report them using the 'Report Abuse' button." + }, + { + optionText: "Don't tell them anything and click the 'Report Abuse' button.", + passable: true, + doorResponse: "Correct! Press the 'Report Abuse' button and fill in the offending player's name and the correct category." + } + ] + }, + { + questionText: "What are recovery questions used for?", + options: [ + { + optionText: "Recovering your account if it is stolen.", + passable: false, + doorResponse: "INCORRECTRESPONSE" + }, + { + optionText: "Recovering your account if you forget your password.", + passable: true, + doorResponse: "Correct! If you set recovery questions that you can remember, you can use them to set a new password on your account if you forget the current one. Don't use personal details for your recoveries." + }, + { + optionText: "Recovering your billing details.", + passable: false, + doorResponse: "INCORRECTRESPONSE" + } + ] + } + ] +} diff --git a/data/config/travel-locations-data.yaml b/data/config/travel-locations-data.yaml index 8ea3f67d6..229ef4141 100644 --- a/data/config/travel-locations-data.yaml +++ b/data/config/travel-locations-data.yaml @@ -1034,6 +1034,22 @@ x: 2329 y: 5097 z: 0 +- name: Vault of War + x: 1859 + y: 5243 + z: 0 +- name: Catacomb of Famine + x: 2042 + y: 5245 + z: 0 +- name: Pit of Pestilence + x: 2123 + y: 5251 + z: 0 +- name: Sepulchre of Death + x: 2358 + y: 5215 + z: 0 - name: Wizards' Guild x: 2583 y: 3078 diff --git a/data/config/xteas/455.json b/data/config/xteas/455.json new file mode 100644 index 000000000..f8bad3056 --- /dev/null +++ b/data/config/xteas/455.json @@ -0,0 +1,15 @@ +[ + { + "archive": 5, + "group": 779, + "name_hash": -1154306995, + "name": "l33_82", + "mapsquare": 8530, + "key": [ + -944370155, + 454273692, + -1617260039, + -2120635933 + ] + } +] \ No newline at end of file diff --git a/src/game-engine/config/index.ts b/src/game-engine/config/index.ts index 411d9a3ef..f66113c7f 100644 --- a/src/game-engine/config/index.ts +++ b/src/game-engine/config/index.ts @@ -20,6 +20,11 @@ import { ItemSpawn, loadItemSpawnConfigurations } from '@engine/config/item-spaw import { loadSkillGuideConfigurations, SkillGuide } from '@engine/config/skill-guide-config'; import { loadMusicRegionConfigurations, MusicTrack } from '@engine/config/music-regions-config'; import { loadXteaRegionFiles, XteaRegion } from '@runejs/filestore'; +import { + loadStrongholdOfSecurityQuizData, + StrongholdOfSecurityQuestion, + StrongholdOfSecurityQuiz +} from '@plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin'; require('json5/lib/register'); @@ -37,6 +42,7 @@ export let itemSpawns: ItemSpawn[] = []; export let shopMap: { [key: string]: Shop }; export let skillGuides: SkillGuide[] = []; export let xteaRegions: { [key: number]: XteaRegion }; +export let strongholdOfSecurityQuizData: StrongholdOfSecurityQuiz; export const musicRegionMap = new Map(); export const widgets: { [key: string]: any } = require('../../../data/config/widgets.json5'); @@ -58,6 +64,7 @@ export async function loadGameConfigurations(): Promise { npcIdMap = npcIds; npcPresetMap = npcPresets; + strongholdOfSecurityQuizData = await loadStrongholdOfSecurityQuizData(`data/config/stronghold-of-security-quiz.json5`); npcSpawns = await loadNpcSpawnConfigurations('data/config/npc-spawns/'); musicRegions = await loadMusicRegionConfigurations(); musicRegions.forEach(song => song.regionIds.forEach(region => musicRegionMap.set(region, song.songId))); @@ -65,6 +72,7 @@ export async function loadGameConfigurations(): Promise { shopMap = await loadShopConfigurations('data/config/shops/'); skillGuides = await loadSkillGuideConfigurations('data/config/skill-guides/'); + logger.info(`Loaded ${strongholdOfSecurityQuizData} Stronghold of Security questions.`); logger.info(`Loaded ${musicRegions.length} music regions, ${Object.keys(itemMap).length} items, ${itemSpawns.length} item spawns, ` + `${Object.keys(npcMap).length} npcs, ${npcSpawns.length} npc spawns, ${Object.keys(shopMap).length} shops and ${skillGuides.length} skill guides.`); } @@ -194,3 +202,8 @@ export const findMusicTrackByButtonId = (buttonId: number): MusicTrack | null => export const findSongIdByRegionId = (regionId: number): number | null => { return musicRegionMap.has(regionId) ? musicRegionMap.get(regionId) : null; }; + +export function getRandomStrongholdOfSecurityQuestion(): StrongholdOfSecurityQuestion | null { + const randomIndex = Math.floor(Math.random() * strongholdOfSecurityQuizData.questions.length); + return strongholdOfSecurityQuizData.questions[randomIndex]; +} diff --git a/src/game-engine/util/strings.ts b/src/game-engine/util/strings.ts index c00c17a10..8a96cbd36 100644 --- a/src/game-engine/util/strings.ts +++ b/src/game-engine/util/strings.ts @@ -1,4 +1,6 @@ import { hexToHexString } from '@engine/util/colors'; +import { FontName } from '@runejs/filestore'; +import { filestore } from '@engine/game-server'; export const startsWithVowel = (str: string): boolean => { str = str.trim().toLowerCase(); @@ -23,46 +25,141 @@ const charWidths = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 13, 6, 8, 8, 8, 8, 4, 4, 5, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]; -export function wrapText(text: string, maxWidth: number): string[] { - const lines = []; +export enum TextDecoration { + Color, + Decoration +} - let lineStartIdx = 0; - let width = 0; - let lastSpace = 0; - let widthAfterSpace = 0; - let lastSpaceChar = ''; - for (let i = 0; i < text.length; i++) { - const char = text.charAt(i); +function getFont(font: number | string) { + if (font && typeof font === 'number') { + return filestore.fontStore.getFontById(font); + } else if (font && typeof font === 'string') { + return filestore.fontStore.getFontByName(FontName[font]); + } else { + // Default font, subject to change + return filestore.fontStore.getFontByName(FontName.p12_full); + } +} - // Ignore and strings... - if (char === '<' && (text.charAt(i + 1) === '/' || text.charAt(i + 1) === 'c' && text.charAt(i + 2) === 'o' && text.charAt(i + 3) === 'l')) { - const tagCloseIndex = text.indexOf('>', i); - i = tagCloseIndex; - continue; - } +function getStylingType(tag: string) { + let _tag = tag; + if (_tag.charAt(0) === '/') { + _tag = _tag.substring(1); + } - const charWidth = charWidths[text.charCodeAt(i)]; - width += charWidth; - widthAfterSpace += charWidth; + if (_tag.startsWith('col')) { + return TextDecoration.Color; + } else { + return TextDecoration.Decoration; + } +} - if (char === ' ' || char === '\n' || char === '-') { - lastSpaceChar = char; - lastSpace = i; - widthAfterSpace = 0; +// TODO refactor a bit +export function wrapText(text: string, maxWidth: number, font?: number | string): string[] { + const lines = []; + const selectedFont = getFont(font); + const colorQueue: string[] = []; + const decorationQueue: string[] = []; + const remainingText = text.split('').reverse(); + let currentLine = ''; + let currentWidth = 0; + let currentTagIndex = -1; + + while (remainingText.length > 0) { + const char = remainingText.pop(); + + let hidden = false; + let rendered = true; + + switch (char) { + case '<': + hidden = true; + currentTagIndex = currentLine.length + 1; + break; + case '>': + hidden = true; + // eslint-disable-next-line no-case-declarations + const currentTag = currentLine.substring(currentTagIndex, currentLine.length); + currentTagIndex = -1; + // eslint-disable-next-line no-case-declarations + const isClosing = currentTag.charAt(0) === '/'; + // eslint-disable-next-line no-case-declarations + const type = getStylingType(currentTag); + if (type === TextDecoration.Decoration) { + if (!isClosing) { + decorationQueue.push(currentTag); + } else { + decorationQueue.pop(); + } + } else { + if (!isClosing) { + colorQueue.push(currentTag); + } else { + colorQueue.pop(); + } + } + break; + case '@': + break; + case '\n': + hidden = true; + currentWidth = maxWidth; + rendered = false; + break; + case ' ': + if (currentLine[currentLine.length - 1] === ' ' || currentWidth === 0) { + hidden = true; + rendered = false; + } + break; + default: + break; + } + if (rendered) { + currentLine += char; + } + if (!hidden && currentTagIndex == -1) { + const charWidth = selectedFont.getCharWidth(char); + currentWidth += charWidth; } - if (width >= maxWidth || char === '\n') { - lines.push(text.substring(lineStartIdx, lastSpaceChar === '-' ? lastSpace + 1 : lastSpace)); - lineStartIdx = lastSpace + 1; - width = widthAfterSpace; + if (currentWidth >= maxWidth) { + let lastSpace = currentLine.lastIndexOf(' '); + const lastTag = currentLine.lastIndexOf('<'); + if (lastTag > lastSpace && char !== '\n') { + lastSpace = lastTag; + const type = getStylingType(currentLine.substring(lastTag + 1)); + if (type === TextDecoration.Decoration) { + decorationQueue.pop(); + } else { + colorQueue.pop(); + } + } + let lineToPush = currentLine; + let remainder = ''; + if (lastSpace != -1 && char != '\n') { + lineToPush = lineToPush.substring(0, lastSpace); + remainder = currentLine.substring(lastSpace); + } + + decorationQueue.slice(0).reverse().map(tag => lineToPush += ``); + colorQueue.slice(0).reverse().map(tag => lineToPush += ``); + lines.push(lineToPush.trim()); + currentLine = ''; + decorationQueue.slice(0).map(tag => currentLine += `<${tag}>`); + colorQueue.slice(0).map(tag => currentLine += `<${tag}>`); + remainingText.push(...remainder.split('').reverse()) + currentWidth = 0; } + } + if(currentLine !== '\n') { + lines.push(currentLine); - if (lineStartIdx !== text.length - 1) { - lines.push(text.substring(lineStartIdx, text.length)); } + // logger.info('split lines: ' + lines) return lines; } diff --git a/src/game-engine/world/actor/actor.ts b/src/game-engine/world/actor/actor.ts index 34fb18733..e86539156 100644 --- a/src/game-engine/world/actor/actor.ts +++ b/src/game-engine/world/actor/actor.ts @@ -91,10 +91,11 @@ export abstract class Actor { * Waits for the actor to reach the specified game object before resolving it's promise. * The promise will be rejected if the actor's walking queue changes or their movement is otherwise canceled. * @param target The position or game object that the actor needs to reach for the promise to resolve. + * @param ignoreInteractionDistanceRequirement Whether or not to disregard the fact that the actor is within interaction distance. */ - public async waitForPathing(target: Position | LandscapeObject): Promise; - public async waitForPathing(target: Position | LandscapeObject): Promise { - if(this.position.withinInteractionDistance(target)) { + public async waitForPathing(target: Position | LandscapeObject, ignoreInteractionDistanceRequirement?: boolean): Promise; + public async waitForPathing(target: Position | LandscapeObject, ignoreInteractionDistanceRequirement?: boolean): Promise { + if(this.position.withinInteractionDistance(target) && !ignoreInteractionDistanceRequirement) { return; } @@ -206,7 +207,7 @@ export abstract class Actor { if(distance <= 1) { return false; } - + if(distance > 16) { this.clearFaceActor(); this.metadata.faceActorClearedByWalking = true; diff --git a/src/game-engine/world/actor/dialogue.ts b/src/game-engine/world/actor/dialogue.ts index e686b3d47..23188025a 100644 --- a/src/game-engine/world/actor/dialogue.ts +++ b/src/game-engine/world/actor/dialogue.ts @@ -5,6 +5,7 @@ import { logger } from '@runejs/core'; import _ from 'lodash'; import { wrapText } from '@engine/util/strings'; import { findNpc } from '@engine/config'; +import { ParentWidget, TextWidget } from '@runejs/filestore'; export enum Emote { @@ -111,8 +112,29 @@ const continuableTextWidgetIds = [ 210, 211, 212, 213, 214 ]; const textWidgetIds = [ 215, 216, 217, 218, 219 ]; const titledTextWidgetId = 372; +/** + * Wraps dialogue text into multiple lines. + * @param text - The text to wrap. + * @param type - 'ACTOR' if the widget has a chat-head or an item sprite on the left, 'TEXT' if the dialogue is text only + */ function wrapDialogueText(text: string, type: 'ACTOR' | 'TEXT'): string[] { - return wrapText(text, type === 'ACTOR' ? 340 : 430); + let widget: TextWidget; + let width = 0; + + switch (type) { + case 'ACTOR': + widget = (filestore.widgetStore.decodeWidget(playerWidgetIds[0]) as ParentWidget).children[2] as TextWidget; + width = widget.width; + break; + case 'TEXT': + widget = filestore.widgetStore.decodeWidget(textWidgetIds[0]) as TextWidget; + width = widget.width; + break; + default: + throw new Error(`Unhandled widget type: ${type}`); + } + + return wrapText(text, width, widget.fontId); } function parseDialogueFunctionArgs(func: Function): string[] { @@ -141,6 +163,7 @@ export type DialogueTree = (Function | DialogueFunction | GoToAction)[]; export interface AdditionalOptions { closeOnWalk?: boolean; permanent?: boolean; + title?: string; } interface NpcParticipant { @@ -205,53 +228,55 @@ interface SubDialogueTreeAction extends DialogueAction { function parseDialogueTree(player: Player, npcParticipants: NpcParticipant[], dialogueTree: DialogueTree): ParsedDialogueTree { const parsedDialogueTree: ParsedDialogueTree = []; - for(let i = 0; i < dialogueTree.length; i++) { + let carryoverDialogue = []; + for (let i = 0; i < dialogueTree.length; i++) { const dialogueAction = dialogueTree[i]; - if(dialogueAction instanceof DialogueFunction) { + if (dialogueAction instanceof DialogueFunction) { // Code execution dialogue. parsedDialogueTree.push(dialogueAction as DialogueFunction); continue; } - if(dialogueAction instanceof GoToAction) { + if (dialogueAction instanceof GoToAction) { parsedDialogueTree.push(dialogueAction); continue; } let args = parseDialogueFunctionArgs(dialogueAction); - if(args === null) { + if (args === null) { args = ['()']; } + const dialogueType = args[0]; let tag: string = null; - if(args.length === 2 && typeof args[1] === 'string') { + if (args.length === 2 && typeof args[1] === 'string') { player.metadata.dialogueIndices[args[1]] = i; tag = args[1]; } - if(!dialogueType) { + if (!dialogueType) { logger.error('No arguments passed to dialogue function.'); continue; } let isOptions = false; - if(dialogueType === 'options' || dialogueType === '()') { + if (dialogueType === 'options' || dialogueType === '()') { // Options or custom function dialogue. let result = dialogueAction(); - if(dialogueType === '()') { + if (dialogueType === '()') { const funcResult = result(); - if(!Array.isArray(funcResult) || funcResult.length === 0) { + if (!Array.isArray(funcResult) || funcResult.length === 0) { logger.error('Invalid dialogue function response type.'); continue; } - if(typeof funcResult[0] === 'function') { + if (typeof funcResult[0] === 'function') { // given function returned a dialogue tree parsedDialogueTree.push(...parseDialogueTree(player, npcParticipants, funcResult)); } else { @@ -263,7 +288,7 @@ function parseDialogueTree(player: Player, npcParticipants: NpcParticipant[], di isOptions = true; } - if(isOptions) { + if (isOptions) { const options = (result as any[]).filter((option, index) => index % 2 === 0); const trees = (result as any[]).filter((option, index) => index % 2 !== 0); const optionsDialogueAction: OptionsDialogueAction = { @@ -271,7 +296,7 @@ function parseDialogueTree(player: Player, npcParticipants: NpcParticipant[], di tag, type: 'OPTIONS' }; - for(let j = 0; j < options.length; j++) { + for (let j = 0; j < options.length; j++) { const option = options[j]; const tree = parseDialogueTree(player, npcParticipants, trees[j]); optionsDialogueAction.options[option] = tree; @@ -279,30 +304,30 @@ function parseDialogueTree(player: Player, npcParticipants: NpcParticipant[], di parsedDialogueTree.push(optionsDialogueAction); } - } else if(dialogueType === 'text') { + } else if (dialogueType === 'text') { // Text-only dialogue (with the option to click continue). const text: string = dialogueAction(); const lines = wrapDialogueText(text, 'TEXT'); parsedDialogueTree.push({ lines, tag, type: 'TEXT', canContinue: true } as TextDialogueAction); - } else if(dialogueType === 'overlay') { + } else if (dialogueType === 'overlay') { // Text-only dialogue (no option to continue). const text: string = dialogueAction(); const lines = wrapDialogueText(text, 'TEXT'); parsedDialogueTree.push({ lines, tag, type: 'TEXT', canContinue: false } as TextDialogueAction); - } else if(dialogueType === 'titled') { + } else if (dialogueType === 'titled') { // Text-only dialogue (no option to continue). - const [ title, text ] = dialogueAction(); + const [title, text] = dialogueAction(); const lines = wrapDialogueText(text, 'TEXT'); - while(lines.length < 4) { + while (lines.length < 4) { lines.push(''); } parsedDialogueTree.push({ lines, title, tag, type: 'TITLED' } as TitledTextDialogueAction); - } else if(dialogueType === 'subtree') { + } else if (dialogueType === 'subtree') { // Dialogue sub-tree. const subTree: DialogueTree = dialogueAction(); @@ -310,19 +335,19 @@ function parseDialogueTree(player: Player, npcParticipants: NpcParticipant[], di } else { // Player or Npc dialogue. - let dialogueDetails: [ Emote, string ]; + let dialogueDetails: [Emote, string]; let npc: Npc | number | string; - if(dialogueType !== 'player') { + if (dialogueType !== 'player') { const participant = npcParticipants.find(p => p.key === dialogueType) as NpcParticipant; - if(!participant || !participant.npc) { + if (!participant || !participant.npc) { logger.error('No matching npc found for npc dialogue action.'); continue; } npc = participant.npc; - if(typeof npc !== 'number') { - if(typeof npc === 'string') { + if (typeof npc !== 'number') { + if (typeof npc === 'string') { npc = findNpc(npc)?.gameId || 0; } else { npc = npc.id; @@ -334,17 +359,46 @@ function parseDialogueTree(player: Player, npcParticipants: NpcParticipant[], di dialogueDetails = dialogueAction(player); } + const emote = dialogueDetails[0] as Emote; - const text = dialogueDetails[1] as string; - const lines = wrapDialogueText(text, 'ACTOR'); + const text = carryoverDialogue.join(' ') + dialogueDetails[1] as string; + carryoverDialogue = []; + + let lines = wrapDialogueText(text, 'ACTOR'); + // logger.info('length = ' + lines.length + ' - lines equals this: ' + lines); + const animation = nonLineEmotes.indexOf(emote) !== -1 ? EmoteAnimation[emote] : EmoteAnimation[`${emote}_${lines.length}LINE`]; - if(dialogueType !== 'player') { - const npcDialogueAction: NpcDialogueAction = { - npcId: npc as number, animation, lines, tag, type: 'NPC' - }; + if (dialogueType !== 'player') { + if (lines.length > 4) { + while (lines.length > 4) { + const copyOfLines = lines.slice(0, lines.length); + lines = lines.slice(0, 4); + + const npcDialogueAction: NpcDialogueAction = { + npcId: npc as number, animation, lines, tag, type: 'NPC' + }; + parsedDialogueTree.push(npcDialogueAction); + + lines = copyOfLines.slice(0, copyOfLines.length); + carryoverDialogue = lines.slice(4, lines.length) as string[]; + lines = carryoverDialogue; + + if(i === dialogueTree.length - 1 && lines.length <= 4) { + const npcDialogueAction: NpcDialogueAction = { + npcId: npc as number, animation, lines, tag, type: 'NPC' + }; + + parsedDialogueTree.push(npcDialogueAction); + } + } + } else { + const npcDialogueAction: NpcDialogueAction = { + npcId: npc as number, animation, lines, tag, type: 'NPC' + }; - parsedDialogueTree.push(npcDialogueAction); + parsedDialogueTree.push(npcDialogueAction); + } } else { const playerDialogueAction: PlayerDialogueAction = { player, animation, lines, tag, type: 'PLAYER' @@ -389,7 +443,6 @@ async function runDialogueAction(player: Player, dialogueAction: string | Dialog isOptions = true; const options = Object.keys(optionsAction.options); const trees = options.map(option => optionsAction.options[option]); - if(tag === undefined || dialogueAction.tag === tag) { tag = undefined; @@ -530,7 +583,6 @@ async function runDialogueAction(player: Player, dialogueAction: string | Dialog }); const widgetClosedEvent = await player.interfaceState.widgetClosed('chatbox'); - if(widgetClosedEvent.data !== undefined) { if(isOptions && typeof widgetClosedEvent.data === 'number') { const optionsAction = dialogueAction as OptionsDialogueAction; diff --git a/src/game-engine/world/actor/player/player.ts b/src/game-engine/world/actor/player/player.ts index 69775019d..ca313b35c 100644 --- a/src/game-engine/world/actor/player/player.ts +++ b/src/game-engine/world/actor/player/player.ts @@ -56,7 +56,6 @@ import { MusicPlayerMode } from '@plugins/music/music-tab.plugin'; import { getVarbitMorphIndex } from '@engine/util/varbits'; import { SendMessageOptions } from '@engine/world/actor/player/model'; - export const playerOptions: { option: string, index: number, placement: 'TOP' | 'BOTTOM' }[] = [ { option: 'Yeet', diff --git a/src/game-engine/world/config/animation-ids.ts b/src/game-engine/world/config/animation-ids.ts index 09727f791..caa1827ac 100644 --- a/src/game-engine/world/config/animation-ids.ts +++ b/src/game-engine/world/config/animation-ids.ts @@ -1,4 +1,9 @@ export const animationIds = { + openWardrobe: 545, + searchObject: 881, + touchStrongholdOfSecurityDoor: 4282, + lookAroundAfterStrongholdTeleportation: 4283, + closeWardrobe: 535, milkCow: 2305, lightingFire: 733, homeTeleportDraw: 4847, diff --git a/src/game-engine/world/config/object-ids.ts b/src/game-engine/world/config/object-ids.ts index 6d26d6724..7848ee579 100644 --- a/src/game-engine/world/config/object-ids.ts +++ b/src/game-engine/world/config/object-ids.ts @@ -8,9 +8,78 @@ export const objectIds = { shortCuts: { stile: 12982 }, + strongholdOfSecurity: { + rewardObjects: { + giftOfPeace: 16135, + grainOfPlenty: 16077, + boxOfHealth: 16118, + cradleOfLife: 16047 + }, + + escapeRopes: { + spikeyChain: 16146, + catacombRope: 16078, + gooCoveredVine: 16112, + boneChain: 16048 + }, + + portals: { + levelOnePortal: 16150, + levelTwoPortal: 16082, + levelThreePortal: 16116, + levelFourPortal: 16050 + }, + + ascendingLadders: { + vaultOfWarLadder: 16148, + catacombLadder: 16080, + drippingVine: 16114, + boneyLadder: 16049 + }, + + descendingLadders: { + vaultOfWarLadder: 16149, + catacombLadder: 16081, + drippingVine: 16115 + }, + + gates: { + gateOfWarLeft: 16123, + gateOfWarRight: 16124, + ricketyDoorLeft: 16065, + ricketyDoorRight: 16066, + oozingBarrierLeft: 16090, + oozingBarrierRight: 16089, + thePortalOfDeathLeft: 16044, + thePortalOfDeathRight: 16043 + }, + + miscellaneous: { + deadExplorer: 16152 + } + }, + + draynorManor: { + wardrobeClosed: 388, + fountain: 153, + skeletonWardrobeOpened: 390, + nonSkeletonWardrobeOpened: 389 + }, + ladders: { taverlyDungeonOverworld: 1759, taverlyDungeonUnderground: 1755, + draynorManorOverworld: 133, + draynorManorUnderground: 132, + }, + + staircases: { + taverlyDungeonOverworld: 1759, + taverlyDungeonUnderground: 1755, + draynorManorGroundLevelStaircaseUp: 11498, + draynorManorGroundLevelStaircaseDown: 11499, + draynorManorSpiralUp: 11511, + draynorManorSpiralDown: 9584 }, brokenCart: 306, brokenCartWheel: 327, diff --git a/src/game-engine/world/config/static-positions.ts b/src/game-engine/world/config/static-positions.ts new file mode 100644 index 000000000..2b07b313a --- /dev/null +++ b/src/game-engine/world/config/static-positions.ts @@ -0,0 +1,13 @@ +import { Position } from '@engine/world/position'; + +export const staticPositions = { + strongholdOfSecurityEntrance: new Position(3081, 3421), + vaultOfWarEntrance: new Position(1858, 5244), + catacombOfFamineEntrance: new Position(2042, 5245), + pitOfPestilenceEntrance: new Position(2123, 5252), + sepulchreOfDeathEntrance: new Position(2358, 5215), + vaultOfWarPortalDestination: new Position(1858, 5244), + catacombOfFaminePortalDestination: new Position(2042, 5245), + pitOfPestilencePortalDestination: new Position(2123, 5252), + sepulchreOfDeathPortalDestination: new Position(2358, 5215) +} diff --git a/src/game-engine/world/config/stronghold-of-security-reward-data.ts b/src/game-engine/world/config/stronghold-of-security-reward-data.ts new file mode 100644 index 000000000..86d416e1f --- /dev/null +++ b/src/game-engine/world/config/stronghold-of-security-reward-data.ts @@ -0,0 +1,22 @@ +import { objectIds } from '@engine/world/config/object-ids'; + +export const strongholdOfSecurityRewardData = { + vaultOfWar: { + objectId: objectIds.strongholdOfSecurity.rewardObjects.giftOfPeace, + initialMessage: `The box hinges creak and appear to be forming audible words....`, + emoteUnlocked: `Flap`, + amountOfGoldRewarded: 2000 + }, + catacombOfFamine: { + objectId: objectIds.strongholdOfSecurity.rewardObjects.grainOfPlenty, + initialMessage: `The wheat shifts in the sack, sighing audible words....`, + emoteUnlocked: `Slap Head`, + amountOfGoldRewarded: 3000 + }, + pitOfPestilence: { + objectId: objectIds.strongholdOfSecurity.rewardObjects.boxOfHealth, + initialMessage: `The box hinges creak and appear to be forming audible words....`, + emoteUnlocked: `Idea`, + amountOfGoldRewarded: 5000 + } +} diff --git a/src/game-engine/world/direction.ts b/src/game-engine/world/direction.ts index c62f4d77c..ee3a44954 100644 --- a/src/game-engine/world/direction.ts +++ b/src/game-engine/world/direction.ts @@ -61,6 +61,18 @@ export const directionData: { [key: string]: DirectionData } = { }; export const WNES: Direction[] = ['WEST', 'NORTH', 'EAST', 'SOUTH']; +export const directionNameFromIndex = (index: number): string => { + const keys = Object.keys(directionData); + for (const key of keys) { + if (directionData[key].rotation === index) { + return key; + } + } + + return null; +}; + + export const directionFromIndex = (index: number): DirectionData => { const keys = Object.keys(directionData); for (const key of keys) { diff --git a/src/plugins/buttons/player-emotes.plugin.ts b/src/plugins/buttons/player-emotes.plugin.ts index 355c2052d..11cd1b4a6 100644 --- a/src/plugins/buttons/player-emotes.plugin.ts +++ b/src/plugins/buttons/player-emotes.plugin.ts @@ -74,7 +74,7 @@ export const emotes: { [key: number]: Emote } = { 32: { animationId: 4276, name: 'IDEA', unlockable: true, graphicId: 712 }, 30: { animationId: 4278, name: 'STAMP', unlockable: true }, 31: { animationId: 4280, name: 'FLAP', unlockable: true }, - 29: { animationId: 4275, name: 'FACEPALM', unlockable: true }, + 29: { animationId: 4275, name: 'SLAP HEAD', unlockable: true }, 33: { animationId: 3544, name: 'ZOMBIE WALK', unlockable: true }, 34: { animationId: 3543, name: 'ZOMBIE DANCE', unlockable: true }, 35: { animationId: 2836, name: 'SCARED', unlockable: true }, @@ -115,7 +115,7 @@ export function unlockEmotes(player: Player): void { goblinConfig += 7; if(name === 'FLAP') sosConfig += 1; - if(name === 'FACEPALM') + if(name === 'SLAP HEAD') sosConfig += 2; if(name === 'IDEA') sosConfig += 4; @@ -153,7 +153,7 @@ export const handler: buttonActionHandler = (details) => { const { player, buttonId } = details; const emote = emotes[buttonId]; - + if(emote.name === 'SKILLCAPE') { if (player.getEquippedItem('back')) { if (skillCapeEmotes.some(item => item.itemIds.includes(player.getEquippedItem('back')?.itemId))) { diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-objects.plugin.ts b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-objects.plugin.ts new file mode 100644 index 000000000..c7e928ad8 --- /dev/null +++ b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-objects.plugin.ts @@ -0,0 +1,284 @@ +import { ObjectInteractionAction, ObjectInteractionActionHook } from '@engine/world/action/object-interaction.action'; +import { TaskExecutor } from '@engine/world/action'; +import { schedule } from '@engine/world/task'; +import { dialogue, execute } from '@engine/world/actor/dialogue'; +import { Position } from '@engine/world/position'; +import { objectIds } from '@engine/world/config/object-ids'; +import { animationIds } from '@engine/world/config/animation-ids'; +import { staticPositions } from '@engine/world/config/static-positions'; + +const canActivate = (task: TaskExecutor, taskIteration: number): boolean => { + const { actor, actionData: { position, object } } = task; + + return true; +} + +const activateDescendingLadders = async (task: TaskExecutor, taskIteration: number): Promise => { + const { player, actionData: { position, object } } = task.getDetails(); + player.face(position); + + let descend = false; + await dialogue([player], [ + options => [ + `Yes, I know that it may be dangerous down there!`, [ + execute(() => { + descend = true; + }) + ], + `No thanks, I don't want to die!`, [ + execute(() => { + player.sendMessage(`Hey2!`) + }) + ] + ], + ], + ); + + if (descend) { + player.playAnimation(animationIds.climbLadder); + await schedule(1); + + let teleportPosition; + switch (object.objectId) { + case objectIds.strongholdOfSecurity.descendingLadders.vaultOfWarLadder: + teleportPosition = staticPositions.catacombOfFamineEntrance; + break; + + case objectIds.strongholdOfSecurity.descendingLadders.catacombLadder: + teleportPosition = staticPositions.pitOfPestilenceEntrance; + break; + + case objectIds.strongholdOfSecurity.descendingLadders.drippingVine: + teleportPosition = staticPositions.sepulchreOfDeathEntrance; + break; + } + player.sendMessage(`You climb down the ladder to the next level.`) + player.teleport(teleportPosition); + } + return true; +} + +const activateEscapeRopesAndAscendingLadders = async (task: TaskExecutor, taskIteration: number): Promise => { + const { player, actionData: { position, object } } = task.getDetails(); + player.face(position); + let teleportPosition; + switch (object.objectId) { + case objectIds.strongholdOfSecurity.ascendingLadders.vaultOfWarLadder: + teleportPosition = staticPositions.strongholdOfSecurityEntrance; + break; + + case objectIds.strongholdOfSecurity.ascendingLadders.catacombLadder: + case objectIds.strongholdOfSecurity.escapeRopes.spikeyChain: + teleportPosition = staticPositions.vaultOfWarEntrance; + break; + + case objectIds.strongholdOfSecurity.ascendingLadders.drippingVine: + case objectIds.strongholdOfSecurity.escapeRopes.catacombRope: + teleportPosition = staticPositions.catacombOfFamineEntrance; + break; + case objectIds.strongholdOfSecurity.ascendingLadders.boneyLadder: + case objectIds.strongholdOfSecurity.escapeRopes.gooCoveredVine: + teleportPosition = staticPositions.pitOfPestilenceEntrance; + break; + + case objectIds.strongholdOfSecurity.escapeRopes.boneChain: + teleportPosition = staticPositions.strongholdOfSecurityEntrance; + break; + } + + const objectType = getObjectType(object.objectId); + + if (objectType === ObjectType.ESCAPE_ROPE) { + await player.sendMessage(`You shin up the rope, squeeze through a passage then climb a ladder.`) + } else { + await player.sendMessage(`You climb up the ladder to the level above.`) + } + + player.playAnimation(animationIds.climbLadder); + await schedule(1); + player.teleport(teleportPosition); + + if (objectType === ObjectType.ESCAPE_ROPE) { + await player.sendMessage(`You climb up the ladder which seems to twist and wind in all directions.`) + } + return true; +} + +const activatePortal = async (task: TaskExecutor, taskIteration: number): Promise => { + const { player, actionData: { position, object } } = task.getDetails(); + player.face(position); + let teleportPosition; + switch (object.objectId) { + case objectIds.strongholdOfSecurity.portals.levelOnePortal: + if (player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.vaultOfWar) { + teleportPosition = new Position(1914, 5222); + } + break; + + case objectIds.strongholdOfSecurity.portals.levelTwoPortal: + if (player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.catacombOfFamine) { + teleportPosition = new Position(2021, 5223); + } + break; + + case objectIds.strongholdOfSecurity.portals.levelThreePortal: + if (player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.pitOfPestilence) { + teleportPosition = new Position(2146, 5287); + } + break; + + case objectIds.strongholdOfSecurity.portals.levelFourPortal: + if (player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath) { + teleportPosition = new Position(2341, 5219); + } + break; + } + + if (!teleportPosition) { + await player.sendMessage(`You are not of sufficient experience to take the shortcut through this level.`); + return false; + } + + switch (task.actionData.option.toLowerCase()) { + case `climb-up`: + + break; + + + } + player.sendMessage(`You enter the portal to be whisked through to the treasure room.`) + player.teleport(teleportPosition); + return true; +} + + +const activateDeadExplorer = async (task: TaskExecutor, taskIteration: number): Promise => { + const { player, actionData: { position, object } } = task.getDetails(); + player.face(position); + player.playAnimation(animationIds.searchObject); + if (player.hasItemInInventory(9004)) { + player.sendMessage(`You don't find anything.`) + } else { + if (!player.inventory.hasSpace()) { + await dialogue([player], [ + text => (`I'd better make room in my inventory first!`) + ]); + } else { + player.giveItem(9004); + await dialogue([player], [ + text => (`You rummage around in the dead explorer's bag.....`), + text => (`You find a book of hand written notes.`) + ]); + } + } + return true; +} + +const onComplete = (task: TaskExecutor): void => { + // task.actor.face(task.actor.position); +}; + +const getObjectType = (objectId: number): ObjectType => { + for (const ascendingLaddersKey in objectIds.strongholdOfSecurity.ascendingLadders) { + const ladders = objectIds.strongholdOfSecurity.ascendingLadders; + if (ladders[ascendingLaddersKey] === objectId) { + return ObjectType.LADDER; + } + } + + for (const escapeRopesKey in objectIds.strongholdOfSecurity.escapeRopes) { + const escapeRopes = objectIds.strongholdOfSecurity.escapeRopes; + if (escapeRopes[escapeRopesKey] === objectId) { + return ObjectType.ESCAPE_ROPE; + } + } + return undefined; +} + +enum ObjectType { + ESCAPE_ROPE, + LADDER +} + +export default { + pluginId: 'rs:stronghold_of_security_objects', + hooks: [ + { + type: 'object_interaction', + options: ['climb-down'], + objectIds: [objectIds.strongholdOfSecurity.descendingLadders.vaultOfWarLadder, + objectIds.strongholdOfSecurity.descendingLadders.catacombLadder, + objectIds.strongholdOfSecurity.descendingLadders.drippingVine], + strength: 'normal', + multi: false, + walkTo: true, + task: { + canActivate, + activate: activateDescendingLadders, + onComplete + } + } as ObjectInteractionActionHook, + { + type: 'object_interaction', + options: ['climb-up'], + objectIds: [objectIds.strongholdOfSecurity.escapeRopes.boneChain, + objectIds.strongholdOfSecurity.escapeRopes.gooCoveredVine, + objectIds.strongholdOfSecurity.escapeRopes.catacombRope, + objectIds.strongholdOfSecurity.escapeRopes.spikeyChain], + strength: 'normal', + multi: false, + walkTo: true, + task: { + canActivate, + activate: activateEscapeRopesAndAscendingLadders, + onComplete + } + } as ObjectInteractionActionHook, + { + type: 'object_interaction', + options: ['climb-up'], + objectIds: [objectIds.strongholdOfSecurity.ascendingLadders.boneyLadder, + objectIds.strongholdOfSecurity.ascendingLadders.drippingVine, + objectIds.strongholdOfSecurity.ascendingLadders.catacombLadder, + objectIds.strongholdOfSecurity.ascendingLadders.vaultOfWarLadder], + strength: 'normal', + multi: false, + walkTo: true, + task: { + canActivate, + activate: activateEscapeRopesAndAscendingLadders, + onComplete + } + } as ObjectInteractionActionHook, + { + type: 'object_interaction', + options: ['search'], + objectIds: [objectIds.strongholdOfSecurity.miscellaneous.deadExplorer], + strength: 'normal', + multi: false, + walkTo: true, + task: { + canActivate, + activate: activateDeadExplorer, + onComplete + } + } as ObjectInteractionActionHook, + { + type: 'object_interaction', + options: ['use'], + objectIds: [objectIds.strongholdOfSecurity.portals.levelOnePortal, + objectIds.strongholdOfSecurity.portals.levelTwoPortal, + objectIds.strongholdOfSecurity.portals.levelThreePortal, + objectIds.strongholdOfSecurity.portals.levelFourPortal + ], + strength: 'normal', + multi: false, + walkTo: true, + task: { + canActivate, + activate: activatePortal, + onComplete + } + } as ObjectInteractionActionHook + ] +}; diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts new file mode 100644 index 000000000..105380846 --- /dev/null +++ b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts @@ -0,0 +1,39 @@ +import { JSON_SCHEMA, safeLoad } from 'js-yaml'; +import { readFileSync } from 'fs'; +import { LandscapeObject } from '@runejs/filestore'; +import { logger } from '@runejs/core'; + +export interface StrongholdOfSecurityQuiz { + prefix: string; + questions: StrongholdOfSecurityQuestion[]; +} + +export interface StrongholdOfSecurityQuestion { + questionText: string; + options: StrongholdQuizOption[]; +} + +export interface StrongholdQuizOption { + optionText: string; + passable: boolean; + doorResponse: string; +} + +export function loadStrongholdOfSecurityQuizData(path: string): StrongholdOfSecurityQuiz | null { + try { + const quiz = safeLoad(readFileSync(path, 'utf8'), + { schema: JSON_SCHEMA }) as StrongholdOfSecurityQuiz; + + if(!quiz) { + throw new Error('Unable to read stronghold of security quiz data.'); + } + + logger.info(`Loaded stronghold of security quiz data! Total questions: ` + quiz.questions.length) + return quiz; + } catch(error) { + logger.error('Error parsing stronghold of security quiz data: ' + error); + } +} +export default { + pluginId: 'rs:stronghold_of_security_quiz', +}; diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin.ts b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin.ts new file mode 100644 index 000000000..8920d9ac6 --- /dev/null +++ b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin.ts @@ -0,0 +1,247 @@ +import { ObjectInteractionAction, ObjectInteractionActionHook } from '@engine/world/action/object-interaction.action'; +import { TaskExecutor } from '@engine/world/action'; +import { dialogue, Emote, execute } from '@engine/world/actor/dialogue'; +import { findItem } from '@engine/config'; +import { unlockEmote } from '@plugins/buttons/player-emotes.plugin'; +import { objectIds } from '@engine/world/config/object-ids'; +import { Player } from '@engine/world/actor/player/player'; +import { strongholdOfSecurityRewardData } from '@engine/world/config/stronghold-of-security-reward-data'; + +const canActivate = (task: TaskExecutor, taskIteration: number): boolean => { + const { actor, actionData: { position, object } } = task; + + return true; +} + +interface StrongholdOfSecurityRewardData { + objectId: number; + initialMessage: string; + emoteUnlocked: string; + amountOfGoldRewarded: number; +} + +export const getFloorCompletionFromObjectId = (player: Player, objectId: number): boolean => { + switch (objectId) { + case objectIds.strongholdOfSecurity.gates.gateOfWarLeft: + case objectIds.strongholdOfSecurity.gates.gateOfWarRight: + case objectIds.strongholdOfSecurity.rewardObjects.giftOfPeace: + return player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.vaultOfWar; + + case objectIds.strongholdOfSecurity.gates.ricketyDoorLeft: + case objectIds.strongholdOfSecurity.gates.ricketyDoorRight: + case objectIds.strongholdOfSecurity.rewardObjects.grainOfPlenty: + return player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.catacombOfFamine; + + case objectIds.strongholdOfSecurity.gates.oozingBarrierLeft: + case objectIds.strongholdOfSecurity.gates.oozingBarrierRight: + case objectIds.strongholdOfSecurity.rewardObjects.boxOfHealth: + return player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.pitOfPestilence; + + case objectIds.strongholdOfSecurity.gates.thePortalOfDeathLeft: + case objectIds.strongholdOfSecurity.gates.thePortalOfDeathRight: + case objectIds.strongholdOfSecurity.rewardObjects.cradleOfLife: + return player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath; + } +} + +export const getNpcKeyFromObjectId = (objectId: number): string => { + switch (objectId) { + case objectIds.strongholdOfSecurity.gates.gateOfWarLeft: + case objectIds.strongholdOfSecurity.gates.gateOfWarRight: + return `rs:Gate of War`; + + case objectIds.strongholdOfSecurity.gates.ricketyDoorLeft: + case objectIds.strongholdOfSecurity.gates.ricketyDoorRight: + return `rs:Ricketty door`; + + case objectIds.strongholdOfSecurity.gates.oozingBarrierLeft: + case objectIds.strongholdOfSecurity.gates.oozingBarrierRight: + return `rs:Oozing barrier`; + + case objectIds.strongholdOfSecurity.gates.thePortalOfDeathLeft: + case objectIds.strongholdOfSecurity.gates.thePortalOfDeathRight: + return `rs:Portal of Death`; + } +} + + +const setFloorCompletionFromObjectId = (player: Player, objectId: number, completion: boolean): void => { + switch (objectId) { + case objectIds.strongholdOfSecurity.rewardObjects.giftOfPeace: + player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.vaultOfWar = completion; + break; + + case objectIds.strongholdOfSecurity.rewardObjects.grainOfPlenty: + player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.catacombOfFamine = completion; + break; + + case objectIds.strongholdOfSecurity.rewardObjects.boxOfHealth: + player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.pitOfPestilence = completion; + break; + + case objectIds.strongholdOfSecurity.rewardObjects.cradleOfLife: + player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath = completion; + break; + } +} + +const getRewardDataFromObjectId = (objectId: number): StrongholdOfSecurityRewardData => { + for (const key in strongholdOfSecurityRewardData) { + const rewardData = strongholdOfSecurityRewardData[key]; + if(rewardData.objectId === objectId) { + return rewardData; + } + } +} + +const activate = async (task: TaskExecutor, taskIteration: number): Promise => { + const { player, actionData: { position, object } } = task.getDetails(); + + player.face(position); + + const floorComplete = getFloorCompletionFromObjectId(player, object.objectId); + + if (floorComplete && object.objectId !== objectIds.strongholdOfSecurity.rewardObjects.cradleOfLife) { + await dialogue([player], [ + text => ('You have already claimed your reward from this level.') + ]); + return true; + } + + + + const completedSepulchre = player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath; + + if (object.objectId === objectIds.strongholdOfSecurity.rewardObjects.cradleOfLife) { + const fancyBoots = findItem(`rs:Fancy boots`); + const fightingBoots = findItem(`rs:Fighting boots`); + + const lostBoots = player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath && + !(player.hasItemOnPerson(fancyBoots.gameId) || player.hasItemOnPerson(fightingBoots.gameId)) + + if (completedSepulchre) { + if (lostBoots) { + await dialogue([player], [ + text => (`As your hand touches the cradle, you hear a voice in your head of a million dead adventurers...`), + (text, lost) => (`You appear to have lost your boots!`), + (text, welcome) => (`....welcome adventurer... you have a choice....`), + text => (`You can choose between these two pairs of boots.`), + text => (`They will both protect your feet exactly the same, however they look very different. You can always come back and get another pair if you lose them, or even swap them for the other style!`), + + options => [ + `I'll take the colourful ones!`, [ + execute(() => { + player.giveItem(findItem(`rs:Fancy boots`).gameId); + }) + ], + + `I'll take the fighting ones!`, [ + execute(() => { + player.giveItem(findItem(`rs:Fighting boots`).gameId); + }) + ] + ], + (text, tag_congrats) => (`Congratulations! You have successfully navigated the Stronghold of Security and learned to secure your account. You have unlocked the 'Stamp Foot' emote. Remember to keep your account secure in the future!`) + ]); + } else { + await dialogue([player], [ + text => (`As your hand touches the cradle, you hear a voice in your head of a million dead adventurers...`), + options => [ + `Yes, I'd like the other pair instead please!`, [ + player => [Emote.HAPPY, `Yes, I'd like the other pair instead please!`], + execute(() => { + if (player.inventory.hasSpace()) { + if (player.hasItemOnPerson(fancyBoots.gameId)) { + player.removeFirstItem(fancyBoots.gameId); + player.giveItem(fightingBoots.gameId); + } else { + player.removeFirstItem(fightingBoots.gameId); + player.giveItem(fancyBoots.gameId); + } + } else { + dialogue([player], [ + player => [Emote.SAD, `Hmm, perhaps I should have some space in my pack before I do that.`] + ]) + } + }), + ], + `No thanks, I'll keep these!`, [ + player => [Emote.SAD, `No thanks, I'll keep these!`] + ] + ] + ]); + } + + + } else { + await dialogue([player], [ + text => (`As your hand touches the cradle, you hear a voice in your head of a million dead adventurers...`), + (text, welcome) => (`....welcome adventurer... you have a choice....`), + text => (`You can choose between these two pairs of boots.`), + text => (`They will both protect your feet exactly the same, however they look very different. You can always come back and get another pair if you lose them, or even swap them for the other style!`), + + options => [ + `I'll take the colourful ones!`, [ + execute(() => { + player.giveItem(findItem(`rs:Fancy boots`).gameId); + unlockEmote(player, `STAMP`); + player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath = true; + }) + ], + + `I'll take the fighting ones!`, [ + execute(() => { + player.giveItem(findItem(`rs:Fighting boots`).gameId); + unlockEmote(player, `STAMP`); + player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath = true; + }) + ] + ], + (text, tag_congrats) => (`Congratulations! You have successfully navigated the Stronghold of Security and learned to secure your account. You have unlocked the 'Stamp Foot' emote. Remember to keep your account secure in the future!`) + ]); + } + } else { + const dialogueData = getRewardDataFromObjectId(object.objectId); + if(!dialogueData) { + await player.sendMessage(`Unable to get reward information for this object. Something is wrong.`); + return false; + } + await dialogue([player], [ + text => (dialogueData.initialMessage), + text => (`...congratulations adventurer, you have been deemed worthy of this reward. You have also unlocked the ` + dialogueData.emoteUnlocked + ` emote!`), + execute(() => { + player.giveItem({ itemId: findItem(`rs:coins`).gameId, amount: dialogueData.amountOfGoldRewarded }); + unlockEmote(player, dialogueData.emoteUnlocked.toUpperCase()); + setFloorCompletionFromObjectId(player, object.objectId, true); + }), + ]); + } + + return true; +} + +const onComplete = (task: TaskExecutor): void => { + +}; + +export default { + pluginId: 'rs:stronghold_of_security_rewards', + hooks: [ + { + type: 'object_interaction', + options: ['open', 'search'], + objectIds: [objectIds.strongholdOfSecurity.rewardObjects.giftOfPeace, + objectIds.strongholdOfSecurity.rewardObjects.grainOfPlenty, + objectIds.strongholdOfSecurity.rewardObjects.boxOfHealth, + objectIds.strongholdOfSecurity.rewardObjects.cradleOfLife], + strength: 'normal', + multi: false, + walkTo: true, + task: { + canActivate, + activate, + onComplete + } + } as ObjectInteractionActionHook + ] +}; diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security.plugin.ts b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security.plugin.ts new file mode 100644 index 000000000..05c5d8c44 --- /dev/null +++ b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security.plugin.ts @@ -0,0 +1,223 @@ +import { ObjectInteractionAction, ObjectInteractionActionHook } from '@engine/world/action/object-interaction.action'; +import { TaskExecutor } from '@engine/world/action'; +import { directionNameFromIndex, WNES } from '@engine/world/direction'; +import { schedule } from '@engine/world/task'; +import { dialogue, DialogueTree, Emote, execute } from '@engine/world/actor/dialogue'; +import { Position } from '@engine/world/position'; +import { getRandomStrongholdOfSecurityQuestion, strongholdOfSecurityQuizData } from '@engine/config'; +import { Player } from '@engine/world/actor/player/player'; +import { StrongholdOfSecurityQuestion } from '@plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin'; +import { objectIds } from '@engine/world/config/object-ids'; +import { + getFloorCompletionFromObjectId, + getNpcKeyFromObjectId +} from '@plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin'; +import { animationIds } from '@engine/world/config/animation-ids'; + +const canActivate = (task: TaskExecutor, taskIteration: number): boolean => { + const { actor, actionData: { position, object } } = task; + if (actor instanceof Player) { + if (!actor.savedMetadata[`strongholdOfSecurityState`]) { + actor.savedMetadata[`strongholdOfSecurityState`] = { + dueForSecurityQuestion: false, + floorCompletion: { + vaultOfWar: false, + catacombOfFamine: false, + pitOfPestilence: false, + sepulchreOfDeath: false + } + }; + } + } + return !(actor.position.distanceBetween(position) > 1); +} + +const activate = async (task: TaskExecutor, taskIteration: number): Promise => { + const { player, actionData: { position, object } } = task.getDetails(); + const objectOrientation = WNES[object.orientation]; + + if (player.position.distanceBetween(position) > 1) { + return false; + } + + if (player.position.distanceBetween(position) === 1) { + await player.waitForPathing(position, true); + } + + const doorOrientation = directionNameFromIndex(object.orientation); + const teleportPosition = position.clone(); + switch (doorOrientation) { + case 'NORTH': + if (player.position.y === position.y) { + teleportPosition.y++; + } + break; + + case 'EAST': + if (player.position.x === position.x) { + teleportPosition.x++; + } + break; + + case 'SOUTH': + if (player.position.y === position.y) { + teleportPosition.y--; + } + break; + + case 'WEST': + if (player.position.x === position.x) { + teleportPosition.x--; + } + break; + } + player.face(teleportPosition); + + const npcKey = getNpcKeyFromObjectId(object.objectId); + const floorCompleted = getFloorCompletionFromObjectId(player, object.objectId); + + if (player.savedMetadata[`strongholdOfSecurityState`].dueForSecurityQuestion && !floorCompleted) { + player.sessionMetadata[`correctAnswer`] = await promptPlayerWithSecurityQuestion(player, 0, object.objectId); + } else { + if (isWelcomeDoor(position) && player.position.y === position.y + 1) { + await dialogue([player, { npc: npcKey, key: 'gate' }], [ + gate => [Emote.GENERIC, `Greetings adventurer. This place is kept safe by the spirits within the doors. As you pass through you will be asked questions about security. Hopefully you will learn much from us.`], + gate => [Emote.GENERIC, `Please pass through and begin your adventure, beware of the various monsters that dwell within.`], + ]); + } + player.sessionMetadata[`correctAnswer`] = true; + } + + + if (player.sessionMetadata[`correctAnswer`]) { + player.sessionMetadata[`correctAnswer`] = false; + player.savedMetadata[`strongholdOfSecurityState`].dueForSecurityQuestion = !player.savedMetadata[`strongholdOfSecurityState`].dueForSecurityQuestion; + player.playAnimation(animationIds.touchStrongholdOfSecurityDoor); + player.playSound(2858); + await schedule(1); + + player.teleport(teleportPosition); + player.playAnimation(animationIds.lookAroundAfterStrongholdTeleportation); + await schedule(5); + } else { + return false; + } + + + return true; +} + +async function promptPlayerWithSecurityQuestion(player: Player, questionAttempt: number, objectId: number): Promise { + while (questionAttempt !== 3) { + if (questionAttempt === 3) { + return true; + } + + const npcKey = getNpcKeyFromObjectId(objectId); + + await dialogue([player, { + npc: npcKey, + key: 'gate' + }], generateStrongholdQuizDialogue(player, getRandomStrongholdOfSecurityQuestion())); + + if (player.sessionMetadata[`correctAnswer`]) { + return true; + } else { + if(!player.sessionMetadata[`strongholdDialogueComplete`]) { + return false; + } + questionAttempt++; + } + } + return true; +} + +const isWelcomeDoor = (objectPosition: Position): boolean => { + const leftWelcomeDoor = new Position(1858, 5238); + const rightWelcomeDoor = new Position(1859, 5238); + + return (objectPosition.equals(leftWelcomeDoor) || objectPosition.equals(rightWelcomeDoor)); +}; + +function generateStrongholdQuizDialogue(player: Player, strongholdQuestion: StrongholdOfSecurityQuestion): DialogueTree { + const questionText = strongholdOfSecurityQuizData.prefix + strongholdQuestion.questionText; + player.sessionMetadata[`strongholdDialogueComplete`] = false; + //TODO: Learn how to create option DialogueTrees, and make this more efficient. + switch (strongholdQuestion.options.length) { + case 2: + return [ + gate => [Emote.GENERIC, questionText], + options => [ + strongholdQuestion.options[0].optionText, [ + gate => [Emote.GENERIC, strongholdQuestion.options[0].doorResponse], + execute(() => player.sessionMetadata[`correctAnswer`] = strongholdQuestion.options[0].passable) + ], + strongholdQuestion.options[1].optionText, [ + gate => [Emote.GENERIC, strongholdQuestion.options[1].doorResponse], + execute(() => player.sessionMetadata[`correctAnswer`] = strongholdQuestion.options[1].passable) + ] + ], + execute(() => { + player.sessionMetadata[`strongholdDialogueComplete`] = true; + }) + ]; + + case 3: + return [ + gate => [Emote.GENERIC, questionText], + options => [ + strongholdQuestion.options[0].optionText, [ + gate => [Emote.GENERIC, strongholdQuestion.options[0].doorResponse], + execute(() => player.sessionMetadata[`correctAnswer`] = strongholdQuestion.options[0].passable) + ], + strongholdQuestion.options[1].optionText, [ + gate => [Emote.GENERIC, strongholdQuestion.options[1].doorResponse], + execute(() => player.sessionMetadata[`correctAnswer`] = strongholdQuestion.options[1].passable) + ], + strongholdQuestion.options[2].optionText, [ + gate => [Emote.GENERIC, strongholdQuestion.options[2].doorResponse], + execute(() => player.sessionMetadata[`correctAnswer`] = strongholdQuestion.options[2].passable) + ] + ], + execute(() => { + player.sessionMetadata[`strongholdDialogueComplete`] = true; + }) + ]; + + default: + return [ + gate => [Emote.GENERIC, `Something went wrong, beep boop!`], + execute(() => player.sessionMetadata[`correctAnswer`] = false) + ]; + } +} + +const onComplete = (task: TaskExecutor): void => { +}; + +export default { + pluginId: 'rs:stronghold_of_security_doors', + hooks: [ + { + type: 'object_interaction', + options: ['open'], + objectIds: [ + objectIds.strongholdOfSecurity.gates.gateOfWarLeft, + objectIds.strongholdOfSecurity.gates.gateOfWarRight, + objectIds.strongholdOfSecurity.gates.ricketyDoorLeft, + objectIds.strongholdOfSecurity.gates.ricketyDoorRight, + objectIds.strongholdOfSecurity.gates.oozingBarrierLeft, + objectIds.strongholdOfSecurity.gates.oozingBarrierRight, + objectIds.strongholdOfSecurity.gates.thePortalOfDeathLeft, + objectIds.strongholdOfSecurity.gates.thePortalOfDeathRight], + strength: 'normal', + multi: false, + walkTo: true, + task: { + canActivate, + activate, + onComplete + } + } as ObjectInteractionActionHook + ] +}; diff --git a/src/plugins/player/login-update-settings.plugin.ts b/src/plugins/player/login-update-settings.plugin.ts index b1d1b0a6d..c8e241c8d 100644 --- a/src/plugins/player/login-update-settings.plugin.ts +++ b/src/plugins/player/login-update-settings.plugin.ts @@ -13,6 +13,7 @@ export const handler: playerInitActionHandler = ({ player }) => { player.outgoingPackets.updateClientConfig(widgetScripts.chatEffects, settings.chatEffectsEnabled ? 0 : 1); player.outgoingPackets.updateClientConfig(widgetScripts.acceptAid, settings.acceptAidEnabled ? 1 : 0); player.outgoingPackets.updateClientConfig(widgetScripts.musicVolume, settings.musicVolume); + player.outgoingPackets.updateClientConfig(widgetScripts.musicPlayerAutoManual, settings.musicPlayerMode); player.outgoingPackets.updateClientConfig(widgetScripts.soundEffectVolume, settings.soundEffectVolume); player.outgoingPackets.updateClientConfig(widgetScripts.areaEffectVolume, settings.areaEffectVolume); player.outgoingPackets.updateClientConfig(widgetScripts.runMode, settings.runEnabled ? 1 : 0); From 5fef25bc89afef9f9fbccf16b461e1e62029a265 Mon Sep 17 00:00:00 2001 From: Spencer Carlson Date: Wed, 12 May 2021 17:26:22 -0700 Subject: [PATCH 2/7] Remove: Unnecessary debug messages. --- .../stronghold-of-security-objects.plugin.ts | 6 ++---- .../stronghold-of-security-quiz.plugin.ts | 3 --- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-objects.plugin.ts b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-objects.plugin.ts index c7e928ad8..4aa91468b 100644 --- a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-objects.plugin.ts +++ b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-objects.plugin.ts @@ -1,7 +1,7 @@ import { ObjectInteractionAction, ObjectInteractionActionHook } from '@engine/world/action/object-interaction.action'; import { TaskExecutor } from '@engine/world/action'; import { schedule } from '@engine/world/task'; -import { dialogue, execute } from '@engine/world/actor/dialogue'; +import { dialogue, Emote, execute } from '@engine/world/actor/dialogue'; import { Position } from '@engine/world/position'; import { objectIds } from '@engine/world/config/object-ids'; import { animationIds } from '@engine/world/config/animation-ids'; @@ -26,9 +26,7 @@ const activateDescendingLadders = async (task: TaskExecutor { - player.sendMessage(`Hey2!`) - }) + player => [Emote.SHOCKED, `No thanks, I don't want to die!`] ] ], ], diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts index 105380846..f3a793703 100644 --- a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts +++ b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts @@ -1,6 +1,5 @@ import { JSON_SCHEMA, safeLoad } from 'js-yaml'; import { readFileSync } from 'fs'; -import { LandscapeObject } from '@runejs/filestore'; import { logger } from '@runejs/core'; export interface StrongholdOfSecurityQuiz { @@ -27,8 +26,6 @@ export function loadStrongholdOfSecurityQuizData(path: string): StrongholdOfSecu if(!quiz) { throw new Error('Unable to read stronghold of security quiz data.'); } - - logger.info(`Loaded stronghold of security quiz data! Total questions: ` + quiz.questions.length) return quiz; } catch(error) { logger.error('Error parsing stronghold of security quiz data: ' + error); From 68f89904115ecda2a4f978c9647a935aeaa0a6b1 Mon Sep 17 00:00:00 2001 From: Spencer Carlson Date: Fri, 14 May 2021 11:50:54 -0700 Subject: [PATCH 3/7] Replace: Scape Main login screen music (in arcihve 0) with Scape Scared (archive 321). --- cache/main_file_cache.dat2 | Bin 31268650 -> 31268650 bytes cache/main_file_cache.idx255 | Bin 78 -> 78 bytes cache/main_file_cache.idx6 | Bin 3876 -> 3876 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/cache/main_file_cache.dat2 b/cache/main_file_cache.dat2 index 1d378bd2de61aae8931ede8c72a603fb39cd664f..75e7f71b3caa1a1f9c3a0397079db2935ee06bec 100644 GIT binary patch delta 4683 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 delta 4673 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 diff --git a/cache/main_file_cache.idx255 b/cache/main_file_cache.idx255 index 651e739f5c3f53a2ac71a7f0b0fab9a83425b370..a8419aa2a627133a89fc53e8d37e913728dacfd8 100644 GIT binary patch delta 9 QcmebCo1n%hGErR}01Su%M*si- delta 9 QcmebCo1n(XJyBg901RXTJ^%m! diff --git a/cache/main_file_cache.idx6 b/cache/main_file_cache.idx6 index 4809dc5c0e4c32b5dd52f47f7adb2523c656ca29..355b5cd8f3105fcca58b37151c12c26b66ec5daa 100644 GIT binary patch delta 12 TcmZ1?w?vMaK{kFPvm!qL7CQpy delta 12 TcmZ1?w?vMa!RGr$W<`Dg8SMj~ From 6ca9840d99c66c9c423f1bd67e04da21871f8554 Mon Sep 17 00:00:00 2001 From: Spencer Carlson Date: Fri, 14 May 2021 12:19:41 -0700 Subject: [PATCH 4/7] Fix: Casing on item and NPC definitions to match the standard. --- .../stronghold-of-security/boots.json | 4 ++-- .../npcs/dungeons/stronghold-of-security.json | 8 ++++---- .../stronghold-of-security-rewards.plugin.ts | 20 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/data/config/items/dungeons/stronghold-of-security/boots.json b/data/config/items/dungeons/stronghold-of-security/boots.json index e8c128b86..212047d5a 100644 --- a/data/config/items/dungeons/stronghold-of-security/boots.json +++ b/data/config/items/dungeons/stronghold-of-security/boots.json @@ -1,8 +1,8 @@ { - "rs:Fancy boots": { + "rs:Fancy_boots": { "game_id": 9005 }, - "rs:Fighting boots": { + "rs:Fighting_boots": { "game_id": 9006 } } diff --git a/data/config/npcs/dungeons/stronghold-of-security.json b/data/config/npcs/dungeons/stronghold-of-security.json index 5ff8fc630..1b30981b0 100644 --- a/data/config/npcs/dungeons/stronghold-of-security.json +++ b/data/config/npcs/dungeons/stronghold-of-security.json @@ -1,14 +1,14 @@ { - "rs:Gate of War": { + "rs:Gate_of_War": { "game_id": 4377 }, - "rs:Ricketty door": { + "rs:Ricketty_door": { "game_id": 4378 }, - "rs:Oozing barrier": { + "rs:Oozing_barrier": { "game_id": 4379 }, - "rs:Portal of Death": { + "rs:Portal_of_Death": { "game_id": 4380 }, "rs:Ankou": { diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin.ts b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin.ts index 8920d9ac6..55a4d74de 100644 --- a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin.ts +++ b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin.ts @@ -48,19 +48,19 @@ export const getNpcKeyFromObjectId = (objectId: number): string => { switch (objectId) { case objectIds.strongholdOfSecurity.gates.gateOfWarLeft: case objectIds.strongholdOfSecurity.gates.gateOfWarRight: - return `rs:Gate of War`; + return `rs:Gate_of_War`; case objectIds.strongholdOfSecurity.gates.ricketyDoorLeft: case objectIds.strongholdOfSecurity.gates.ricketyDoorRight: - return `rs:Ricketty door`; + return `rs:Ricketty_door`; case objectIds.strongholdOfSecurity.gates.oozingBarrierLeft: case objectIds.strongholdOfSecurity.gates.oozingBarrierRight: - return `rs:Oozing barrier`; + return `rs:Oozing_barrier`; case objectIds.strongholdOfSecurity.gates.thePortalOfDeathLeft: case objectIds.strongholdOfSecurity.gates.thePortalOfDeathRight: - return `rs:Portal of Death`; + return `rs:Portal_of_Death`; } } @@ -113,8 +113,8 @@ const activate = async (task: TaskExecutor, taskIterati const completedSepulchre = player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath; if (object.objectId === objectIds.strongholdOfSecurity.rewardObjects.cradleOfLife) { - const fancyBoots = findItem(`rs:Fancy boots`); - const fightingBoots = findItem(`rs:Fighting boots`); + const fancyBoots = findItem(`rs:Fancy_boots`); + const fightingBoots = findItem(`rs:Fighting_boots`); const lostBoots = player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath && !(player.hasItemOnPerson(fancyBoots.gameId) || player.hasItemOnPerson(fightingBoots.gameId)) @@ -131,13 +131,13 @@ const activate = async (task: TaskExecutor, taskIterati options => [ `I'll take the colourful ones!`, [ execute(() => { - player.giveItem(findItem(`rs:Fancy boots`).gameId); + player.giveItem(findItem(`rs:Fancy_boots`).gameId); }) ], `I'll take the fighting ones!`, [ execute(() => { - player.giveItem(findItem(`rs:Fighting boots`).gameId); + player.giveItem(findItem(`rs:Fighting_boots`).gameId); }) ] ], @@ -183,7 +183,7 @@ const activate = async (task: TaskExecutor, taskIterati options => [ `I'll take the colourful ones!`, [ execute(() => { - player.giveItem(findItem(`rs:Fancy boots`).gameId); + player.giveItem(findItem(`rs:Fancy_boots`).gameId); unlockEmote(player, `STAMP`); player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath = true; }) @@ -191,7 +191,7 @@ const activate = async (task: TaskExecutor, taskIterati `I'll take the fighting ones!`, [ execute(() => { - player.giveItem(findItem(`rs:Fighting boots`).gameId); + player.giveItem(findItem(`rs:Fighting_boots`).gameId); unlockEmote(player, `STAMP`); player.savedMetadata[`strongholdOfSecurityState`].floorCompletion.sepulchreOfDeath = true; }) From f5f16e41cf729109c4b87034bc56ab090742742a Mon Sep 17 00:00:00 2001 From: Spencer Carlson Date: Thu, 20 May 2021 18:43:01 -0700 Subject: [PATCH 5/7] Add: A basic book system to display book information on an interface from json5 Stronghold of security book Stronghold of security book item key Fix: Player emotes not closing interfaces. Modify: Movement radius of all monsters in the Stronghold of Security. --- data/config/books/security-book.json5 | 35 + ...json => stronghold-of-security-items.json} | 3 + .../dungeons/catacomb-of-famine.json | 254 +++---- .../dungeons/stronghold-of-security.json | 644 +++++++++--------- data/config/widgets.json5 | 36 +- src/game-engine/config/index.ts | 58 +- .../config/sectioned-book-config.ts | 148 ++++ .../stronghold-of-security-quiz-config.ts} | 19 +- src/game-engine/world/config/item-ids.ts | 5 + src/plugins/buttons/player-emotes.plugin.ts | 2 +- .../stronghold-of-security-book.plugin.ts | 348 ++++++++++ .../stronghold-of-security.plugin.ts | 8 +- .../helpers/rotten-potato-travel.ts | 3 +- 13 files changed, 1074 insertions(+), 489 deletions(-) create mode 100644 data/config/books/security-book.json5 rename data/config/items/dungeons/stronghold-of-security/{boots.json => stronghold-of-security-items.json} (65%) create mode 100644 src/game-engine/config/sectioned-book-config.ts rename src/{plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts => game-engine/config/stronghold-of-security-quiz-config.ts} (67%) create mode 100644 src/plugins/dungeons/stronghold-of-security/stronghold-of-security-book.plugin.ts diff --git a/data/config/books/security-book.json5 b/data/config/books/security-book.json5 new file mode 100644 index 000000000..854677b35 --- /dev/null +++ b/data/config/books/security-book.json5 @@ -0,0 +1,35 @@ +{ + bookId: 9004, + bookTitle: "Stronghold Notes", + showTableOfContents: true, + bookSections: [ + { + header: "Description", + text: "This stronghold was unearthed by a miner prospecting for new ores around the Barbarian Village. After gathering some equipment he ventured into the maze of tunnels and was missing for a long time. He finally emerged along with copious notes regarding the new beasts and strange experiences which had befallen him. He also mentioned that there was treasure to be had, but no one has been able to wring a word from him about this, he simply flapped his arms and slapped his head. This book details his notes and my diary of exploration. I am exploring to see if I can find out more..." + }, + { + header: "Level 1", + text: "As well as goblins, creatures like a man but also like a cow infest this place! I have never seen anything like this before. The area itself is reminiscent of frontline castles, with many walls, doors, and skeletons of dead enemies. I'm sure I hear voices in my head each time I pass through the gates. I have dubbed this level War as it seems like an eternal battleground. I found only one small peaceful area here." + }, + { + header: "Level 2", + text: "My supplies are running low and I find myself in barren passages with seemingly endless malnourished beasts attacking me, ravenous for food. Nothing appears to be able to grow, many adventurers have died through lack of food and the very air appears to such vitality from me. I've come to call this place famine.", + }, + { + header: "Level 3", + text: "Just breathing in this place makes me shudder at the thought of what foul disease I may contract. The walls and floor ooze and pulsate like something pox ridden. There is a very strange beast whom I narrowly escaped from. At first I thought it to be a cross between a cow and a sheep, something domesticated... but when it looked up at me I was overcome with weakness and barely got away with my life! Luckily I found a small place where I could heal myself and rest a while. I have named this area pestilence for it reeks with decay." + }, + { + header: "Level 4", + text: "On my first escapade into this place, I was utterly shocked. The adventurers who had come before me must have made up a tiny proportion of the skeletons of the dead. Nothing truly alive exists here, even those beings who do wander the halls are not alive as such, but they do know that I am and I get the distinct impression that were they to have their way, I would not be for long! Death is everywhere and thus I shall name this place. There is one small place of life, which was gladdening to find and very worth my while!" + }, + { + header: "Navigation", + text: "After getting lost several times I finally worked out the key to all the ladders and chains around this death infested place. All ropes and chains will take you to the start of the level that you are on. However most ladders will simply take you to the level above. The one exception is the ladder in the bottom level treasure room, which appears to lead through several extremely twisty passages and eventually takes you out of the dungeon completely. The portals may be used if you are of sufficient level or have already claimed your reward from the treasure room." + }, + { + header: "Diary", + text: "Day 1\nToday I set out to find out more about this place. From my research I knew about the strange creatures, so I have prepared with some good armour.\n\nDay 2\nI have fought my way through the fearsome beasts on the first level and am preparing myself to journey deeper. I hope that things are not too difficult further on as I am already sick of bread and cheese for dinner.\n\nDay 3\nI ventured down into the famine level today... I was wounded and have returned to the relative safety of the level above. I am going to try to make my way out through the goblins and mancow things... I hope I can make it..." + } + ] +} diff --git a/data/config/items/dungeons/stronghold-of-security/boots.json b/data/config/items/dungeons/stronghold-of-security/stronghold-of-security-items.json similarity index 65% rename from data/config/items/dungeons/stronghold-of-security/boots.json rename to data/config/items/dungeons/stronghold-of-security/stronghold-of-security-items.json index 212047d5a..d07870b15 100644 --- a/data/config/items/dungeons/stronghold-of-security/boots.json +++ b/data/config/items/dungeons/stronghold-of-security/stronghold-of-security-items.json @@ -1,4 +1,7 @@ { + "rs:Stronghold_notes": { + "game_id": 9004 + }, "rs:Fancy_boots": { "game_id": 9005 }, diff --git a/data/config/npc-spawns/dungeons/catacomb-of-famine.json b/data/config/npc-spawns/dungeons/catacomb-of-famine.json index 5e6084718..1dc8da70a 100644 --- a/data/config/npc-spawns/dungeons/catacomb-of-famine.json +++ b/data/config/npc-spawns/dungeons/catacomb-of-famine.json @@ -1,7 +1,7 @@ [ { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -10,7 +10,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -19,7 +19,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -28,7 +28,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -37,7 +37,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -46,7 +46,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -55,7 +55,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -64,7 +64,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -73,7 +73,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -82,7 +82,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -91,7 +91,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -100,7 +100,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -109,7 +109,7 @@ }, { "npc": "rs:Flesh Crawler_2", - "movement_radius": 6, + "movement_radius": 10, "face": "WEST", "id": 2500, "spawn_level": 0, @@ -122,7 +122,7 @@ "spawn_y": 5193, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler_1", @@ -130,7 +130,7 @@ "spawn_y": 5187, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler_1", @@ -138,7 +138,7 @@ "spawn_y": 5186, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler_1", @@ -146,7 +146,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler_1", @@ -154,7 +154,7 @@ "spawn_y": 5192, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler_1", @@ -162,7 +162,7 @@ "spawn_y": 5186, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler_1", @@ -170,7 +170,7 @@ "spawn_y": 5189, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler_1", @@ -178,7 +178,7 @@ "spawn_y": 5193, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler", @@ -186,7 +186,7 @@ "spawn_y": 5237, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler", @@ -194,7 +194,7 @@ "spawn_y": 5237, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler", @@ -202,7 +202,7 @@ "spawn_y": 5231, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler", @@ -210,7 +210,7 @@ "spawn_y": 5231, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Flesh Crawler", @@ -218,7 +218,7 @@ "spawn_y": 5233, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Giant rat_7", @@ -226,7 +226,7 @@ "spawn_y": 5189, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Giant rat_7", @@ -234,7 +234,7 @@ "spawn_y": 5192, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { @@ -243,7 +243,7 @@ "spawn_y": 5236, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { @@ -252,7 +252,7 @@ "spawn_y": 5211, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Giant rat_7", @@ -260,7 +260,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Giant rat_7", @@ -268,7 +268,7 @@ "spawn_y": 5188, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Giant rat_7", @@ -276,7 +276,7 @@ "spawn_y": 5235, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Giant rat_7", @@ -284,7 +284,7 @@ "spawn_y": 5215, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Giant rat_7", @@ -292,7 +292,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Giant rat_7", @@ -300,7 +300,7 @@ "spawn_y": 5238, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -308,7 +308,7 @@ "spawn_y": 5202, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -316,7 +316,7 @@ "spawn_y": 5200, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -324,7 +324,7 @@ "spawn_y": 5205, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -332,7 +332,7 @@ "spawn_y": 5223, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -340,7 +340,7 @@ "spawn_y": 5226, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -348,7 +348,7 @@ "spawn_y": 5236, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -356,7 +356,7 @@ "spawn_y": 5228, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -364,7 +364,7 @@ "spawn_y": 5237, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -372,7 +372,7 @@ "spawn_y": 5233, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -380,7 +380,7 @@ "spawn_y": 5200, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -388,7 +388,7 @@ "spawn_y": 5242, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -396,7 +396,7 @@ "spawn_y": 5191, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -404,7 +404,7 @@ "spawn_y": 5199, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -412,7 +412,7 @@ "spawn_y": 5235, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -420,7 +420,7 @@ "spawn_y": 5207, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -428,7 +428,7 @@ "spawn_y": 5211, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -436,7 +436,7 @@ "spawn_y": 5218, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -444,7 +444,7 @@ "spawn_y": 5214, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -452,7 +452,7 @@ "spawn_y": 5239, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -460,7 +460,7 @@ "spawn_y": 5187, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -468,7 +468,7 @@ "spawn_y": 5186, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -476,7 +476,7 @@ "spawn_y": 5189, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -484,7 +484,7 @@ "spawn_y": 5204, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -492,7 +492,7 @@ "spawn_y": 5207, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -500,7 +500,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -508,7 +508,7 @@ "spawn_y": 5203, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -516,7 +516,7 @@ "spawn_y": 5187, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -524,7 +524,7 @@ "spawn_y": 5194, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -532,7 +532,7 @@ "spawn_y": 5236, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -540,7 +540,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -548,7 +548,7 @@ "spawn_y": 5192, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -556,7 +556,7 @@ "spawn_y": 5186, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -564,7 +564,7 @@ "spawn_y": 5192, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -572,7 +572,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -580,7 +580,7 @@ "spawn_y": 5238, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -588,7 +588,7 @@ "spawn_y": 5188, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -596,7 +596,7 @@ "spawn_y": 5193, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -604,7 +604,7 @@ "spawn_y": 5188, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -612,7 +612,7 @@ "spawn_y": 5192, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -620,7 +620,7 @@ "spawn_y": 5232, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -628,7 +628,7 @@ "spawn_y": 5185, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -636,7 +636,7 @@ "spawn_y": 5235, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -644,7 +644,7 @@ "spawn_y": 5244, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -652,7 +652,7 @@ "spawn_y": 5191, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -660,7 +660,7 @@ "spawn_y": 5243, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -668,7 +668,7 @@ "spawn_y": 5230, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -676,7 +676,7 @@ "spawn_y": 5242, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -684,7 +684,7 @@ "spawn_y": 5242, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -692,7 +692,7 @@ "spawn_y": 5246, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -700,7 +700,7 @@ "spawn_y": 5234, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -708,7 +708,7 @@ "spawn_y": 5244, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -716,7 +716,7 @@ "spawn_y": 5202, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -724,7 +724,7 @@ "spawn_y": 5207, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -732,7 +732,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -740,7 +740,7 @@ "spawn_y": 5186, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -748,7 +748,7 @@ "spawn_y": 5200, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -756,7 +756,7 @@ "spawn_y": 5204, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -764,7 +764,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -772,7 +772,7 @@ "spawn_y": 5193, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Rat_16", @@ -780,7 +780,7 @@ "spawn_y": 5201, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_18", @@ -788,7 +788,7 @@ "spawn_y": 5236, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_18", @@ -796,7 +796,7 @@ "spawn_y": 5230, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_18", @@ -804,7 +804,7 @@ "spawn_y": 5235, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_18", @@ -812,7 +812,7 @@ "spawn_y": 5234, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_18", @@ -820,7 +820,7 @@ "spawn_y": 5233, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -828,7 +828,7 @@ "spawn_y": 5192, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -836,7 +836,7 @@ "spawn_y": 5188, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -844,7 +844,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -852,7 +852,7 @@ "spawn_y": 5200, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -860,7 +860,7 @@ "spawn_y": 5201, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -868,7 +868,7 @@ "spawn_y": 5209, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -876,7 +876,7 @@ "spawn_y": 5212, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -884,7 +884,7 @@ "spawn_y": 5215, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -892,7 +892,7 @@ "spawn_y": 5218, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -900,7 +900,7 @@ "spawn_y": 5213, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -908,7 +908,7 @@ "spawn_y": 5218, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -916,7 +916,7 @@ "spawn_y": 5213, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -924,7 +924,7 @@ "spawn_y": 5215, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -932,7 +932,7 @@ "spawn_y": 5215, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_19", @@ -940,7 +940,7 @@ "spawn_y": 5217, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -948,7 +948,7 @@ "spawn_y": 5242, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -956,7 +956,7 @@ "spawn_y": 5234, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -964,7 +964,7 @@ "spawn_y": 5238, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -972,7 +972,7 @@ "spawn_y": 5190, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -980,7 +980,7 @@ "spawn_y": 5187, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -988,7 +988,7 @@ "spawn_y": 5193, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -996,7 +996,7 @@ "spawn_y": 5189, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -1004,7 +1004,7 @@ "spawn_y": 5237, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -1012,7 +1012,7 @@ "spawn_y": 5186, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -1020,7 +1020,7 @@ "spawn_y": 5186, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 }, { "npc": "rs:Zombie_20", @@ -1028,6 +1028,6 @@ "spawn_y": 5191, "spawn_level": 0, "face": "WEST", - "movement_radius": 6 + "movement_radius": 10 } ] diff --git a/data/config/npc-spawns/dungeons/stronghold-of-security.json b/data/config/npc-spawns/dungeons/stronghold-of-security.json index 21227a7e6..908ccb1bd 100644 --- a/data/config/npc-spawns/dungeons/stronghold-of-security.json +++ b/data/config/npc-spawns/dungeons/stronghold-of-security.json @@ -1,323 +1,323 @@ [ - {"npc":"rs:Ankou","spawn_level":0,"spawn_x":2355,"spawn_y":5241,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2357,"spawn_y":5240,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2358,"spawn_y":5243,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2358,"spawn_y":5245,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2359,"spawn_y":5239,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2359,"spawn_y":5241,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2361,"spawn_y":5240,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2361,"spawn_y":5242,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2363,"spawn_y":5240,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2324,"spawn_y":5198,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2325,"spawn_y":5197,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2326,"spawn_y":5201,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2326,"spawn_y":5205,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2326,"spawn_y":5206,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2327,"spawn_y":5199,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2327,"spawn_y":5202,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2328,"spawn_y":5197,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2329,"spawn_y":5203,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2330,"spawn_y":5195,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2331,"spawn_y":5202,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2315,"spawn_y":5229,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2317,"spawn_y":5226,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2318,"spawn_y":5230,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2320,"spawn_y":5224,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2321,"spawn_y":5232,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2322,"spawn_y":5222,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2322,"spawn_y":5227,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2322,"spawn_y":5229,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2323,"spawn_y":5224,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2324,"spawn_y":5230,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2324,"spawn_y":5236,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2327,"spawn_y":5227,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2326,"spawn_y":5199,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2326,"spawn_y":5208,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2332,"spawn_y":5202,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2350,"spawn_y":5200,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2352,"spawn_y":5195,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2353,"spawn_y":5192,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2355,"spawn_y":5198,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2328,"spawn_y":5205,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2329,"spawn_y":5200,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2348,"spawn_y":5197,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2353,"spawn_y":5203,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2354,"spawn_y":5191,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_18","spawn_level":0,"spawn_x":2311,"spawn_y":5187,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Skeleton_18","spawn_level":0,"spawn_x":2311,"spawn_y":5193,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2352,"spawn_y":5189,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2353,"spawn_y":5197,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2356,"spawn_y":5204,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2357,"spawn_y":5194,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2305,"spawn_y":5233,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2305,"spawn_y":5241,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2306,"spawn_y":5229,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2306,"spawn_y":5243,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2309,"spawn_y":5246,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2311,"spawn_y":5237,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2314,"spawn_y":5237,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2314,"spawn_y":5244,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2315,"spawn_y":5236,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2316,"spawn_y":5227,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2322,"spawn_y":5235,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2325,"spawn_y":5228,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1859,"spawn_y":5193,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1859,"spawn_y":5203,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5192,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5200,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5229,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5231,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1862,"spawn_y":5188,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1862,"spawn_y":5204,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1863,"spawn_y":5227,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1864,"spawn_y":5202,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1865,"spawn_y":5188,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1866,"spawn_y":5204,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1868,"spawn_y":5189,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1869,"spawn_y":5226,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1870,"spawn_y":5192,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1871,"spawn_y":5232,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1872,"spawn_y":5190,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1872,"spawn_y":5218,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1872,"spawn_y":5239,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1873,"spawn_y":5188,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1874,"spawn_y":5200,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1874,"spawn_y":5210,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1876,"spawn_y":5189,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1876,"spawn_y":5216,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1877,"spawn_y":5220,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1879,"spawn_y":5198,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1879,"spawn_y":5230,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1879,"spawn_y":5234,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1880,"spawn_y":5200,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1880,"spawn_y":5239,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1880,"spawn_y":5243,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1881,"spawn_y":5232,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1881,"spawn_y":5241,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1882,"spawn_y":5199,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1882,"spawn_y":5201,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1882,"spawn_y":5243,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1883,"spawn_y":5206,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1883,"spawn_y":5234,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1884,"spawn_y":5230,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1884,"spawn_y":5232,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1885,"spawn_y":5205,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1886,"spawn_y":5231,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1887,"spawn_y":5203,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1889,"spawn_y":5206,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1890,"spawn_y":5242,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1895,"spawn_y":5242,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1898,"spawn_y":5241,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1900,"spawn_y":5240,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1900,"spawn_y":5244,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1904,"spawn_y":5235,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1908,"spawn_y":5203,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1909,"spawn_y":5243,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1910,"spawn_y":5202,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1911,"spawn_y":5205,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1912,"spawn_y":5238,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1913,"spawn_y":5235,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2142,"spawn_y":5252,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2145,"spawn_y":5252,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2145,"spawn_y":5256,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2148,"spawn_y":5253,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2149,"spawn_y":5255,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2152,"spawn_y":5251,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2153,"spawn_y":5254,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2155,"spawn_y":5252,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2158,"spawn_y":5282,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2161,"spawn_y":5281,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2162,"spawn_y":5284,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2164,"spawn_y":5280,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2119,"spawn_y":5296,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2120,"spawn_y":5292,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2120,"spawn_y":5299,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2122,"spawn_y":5291,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2123,"spawn_y":5302,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2127,"spawn_y":5305,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2129,"spawn_y":5302,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2130,"spawn_y":5298,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2163,"spawn_y":5301,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2166,"spawn_y":5303,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2166,"spawn_y":5306,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2167,"spawn_y":5300,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2169,"spawn_y":5303,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2170,"spawn_y":5306,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2120,"spawn_y":5275,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2121,"spawn_y":5273,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2123,"spawn_y":5270,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2123,"spawn_y":5275,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2124,"spawn_y":5272,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2124,"spawn_y":5274,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2125,"spawn_y":5270,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2126,"spawn_y":5274,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2127,"spawn_y":5272,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2129,"spawn_y":5268,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2129,"spawn_y":5270,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2130,"spawn_y":5272,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2131,"spawn_y":5267,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2142,"spawn_y":5256,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2143,"spawn_y":5251,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2145,"spawn_y":5253,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2145,"spawn_y":5306,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2146,"spawn_y":5259,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2146,"spawn_y":5306,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2147,"spawn_y":5255,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2147,"spawn_y":5305,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2147,"spawn_y":5307,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2149,"spawn_y":5304,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2149,"spawn_y":5305,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2149,"spawn_y":5306,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2150,"spawn_y":5307,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2150,"spawn_y":5308,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2151,"spawn_y":5268,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2151,"spawn_y":5305,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2151,"spawn_y":5308,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2152,"spawn_y":5268,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2152,"spawn_y":5306,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5270,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5305,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5306,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5307,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5269,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5271,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5273,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5305,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2155,"spawn_y":5254,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2155,"spawn_y":5305,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2157,"spawn_y":5271,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2157,"spawn_y":5273,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2157,"spawn_y":5274,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2119,"spawn_y":5274,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2120,"spawn_y":5277,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2121,"spawn_y":5276,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2122,"spawn_y":5271,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2124,"spawn_y":5269,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2124,"spawn_y":5273,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2126,"spawn_y":5270,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2126,"spawn_y":5272,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2127,"spawn_y":5269,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2127,"spawn_y":5273,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2128,"spawn_y":5271,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2129,"spawn_y":5267,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2129,"spawn_y":5273,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2150,"spawn_y":5267,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2152,"spawn_y":5270,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2155,"spawn_y":5270,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2155,"spawn_y":5271,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2156,"spawn_y":5273,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2156,"spawn_y":5274,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2165,"spawn_y":5255,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2166,"spawn_y":5256,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2167,"spawn_y":5251,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2167,"spawn_y":5253,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2167,"spawn_y":5255,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2168,"spawn_y":5253,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2168,"spawn_y":5256,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2168,"spawn_y":5257,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2169,"spawn_y":5250,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2169,"spawn_y":5254,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2169,"spawn_y":5255,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2170,"spawn_y":5252,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2170,"spawn_y":5254,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2170,"spawn_y":5256,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2171,"spawn_y":5253,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2172,"spawn_y":5254,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2169,"spawn_y":5290,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2170,"spawn_y":5285,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2171,"spawn_y":5280,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2172,"spawn_y":5274,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2170,"spawn_y":5277,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2170,"spawn_y":5288,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2172,"spawn_y":5283,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2173,"spawn_y":5272,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1862,"spawn_y":5190,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1863,"spawn_y":5189,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1867,"spawn_y":5188,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1870,"spawn_y":5235,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1871,"spawn_y":5191,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1871,"spawn_y":5242,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1872,"spawn_y":5227,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1873,"spawn_y":5212,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1873,"spawn_y":5238,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1874,"spawn_y":5216,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1875,"spawn_y":5214,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1875,"spawn_y":5218,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1878,"spawn_y":5216,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1879,"spawn_y":5218,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1880,"spawn_y":5217,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1880,"spawn_y":5220,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1890,"spawn_y":5195,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1890,"spawn_y":5243,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5189,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5198,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5238,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5241,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1894,"spawn_y":5243,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1897,"spawn_y":5196,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1897,"spawn_y":5244,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1899,"spawn_y":5198,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1899,"spawn_y":5242,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1900,"spawn_y":5208,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1900,"spawn_y":5210,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5191,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5197,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5201,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5206,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5193,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5199,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5204,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5207,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5242,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1904,"spawn_y":5191,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1860,"spawn_y":5205,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1860,"spawn_y":5222,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1860,"spawn_y":5226,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1862,"spawn_y":5201,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1859,"spawn_y":5201,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1860,"spawn_y":5215,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1862,"spawn_y":5220,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1863,"spawn_y":5215,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1863,"spawn_y":5217,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1864,"spawn_y":5218,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1859,"spawn_y":5219,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1861,"spawn_y":5224,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1862,"spawn_y":5228,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1864,"spawn_y":5204,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1884,"spawn_y":5207,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1885,"spawn_y":5203,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1891,"spawn_y":5236,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1894,"spawn_y":5229,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1910,"spawn_y":5236,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1913,"spawn_y":5239,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1876,"spawn_y":5198,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1884,"spawn_y":5201,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1887,"spawn_y":5205,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1893,"spawn_y":5235,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1895,"spawn_y":5228,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1896,"spawn_y":5232,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1911,"spawn_y":5240,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1912,"spawn_y":5244,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1913,"spawn_y":5236,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1877,"spawn_y":5199,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1892,"spawn_y":5226,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1894,"spawn_y":5233,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1905,"spawn_y":5235,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1912,"spawn_y":5242,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1870,"spawn_y":5226,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1871,"spawn_y":5229,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1871,"spawn_y":5236,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1872,"spawn_y":5233,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1873,"spawn_y":5239,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1886,"spawn_y":5188,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1887,"spawn_y":5221,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5187,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5191,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5214,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5217,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1890,"spawn_y":5224,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1892,"spawn_y":5217,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1892,"spawn_y":5220,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1894,"spawn_y":5193,"movement_radius":6,"face":"WEST"} -,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1897,"spawn_y":5189,"movement_radius":6,"face":"WEST"} -] \ No newline at end of file + {"npc":"rs:Ankou","spawn_level":0,"spawn_x":2355,"spawn_y":5241,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2357,"spawn_y":5240,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2358,"spawn_y":5243,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2358,"spawn_y":5245,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2359,"spawn_y":5239,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2359,"spawn_y":5241,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2361,"spawn_y":5240,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2361,"spawn_y":5242,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou","spawn_level":0,"spawn_x":2363,"spawn_y":5240,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2324,"spawn_y":5198,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2325,"spawn_y":5197,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2326,"spawn_y":5201,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2326,"spawn_y":5205,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2326,"spawn_y":5206,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2327,"spawn_y":5199,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2327,"spawn_y":5202,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2328,"spawn_y":5197,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2329,"spawn_y":5203,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2330,"spawn_y":5195,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_1","spawn_level":0,"spawn_x":2331,"spawn_y":5202,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2315,"spawn_y":5229,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2317,"spawn_y":5226,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2318,"spawn_y":5230,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2320,"spawn_y":5224,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2321,"spawn_y":5232,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2322,"spawn_y":5222,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2322,"spawn_y":5227,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2322,"spawn_y":5229,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2323,"spawn_y":5224,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2324,"spawn_y":5230,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2324,"spawn_y":5236,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ankou_2","spawn_level":0,"spawn_x":2327,"spawn_y":5227,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2326,"spawn_y":5199,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2326,"spawn_y":5208,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2332,"spawn_y":5202,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2350,"spawn_y":5200,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2352,"spawn_y":5195,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2353,"spawn_y":5192,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_16","spawn_level":0,"spawn_x":2355,"spawn_y":5198,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2328,"spawn_y":5205,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2329,"spawn_y":5200,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2348,"spawn_y":5197,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2353,"spawn_y":5203,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_17","spawn_level":0,"spawn_x":2354,"spawn_y":5191,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_18","spawn_level":0,"spawn_x":2311,"spawn_y":5187,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Skeleton_18","spawn_level":0,"spawn_x":2311,"spawn_y":5193,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2352,"spawn_y":5189,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2353,"spawn_y":5197,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2356,"spawn_y":5204,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_8","spawn_level":0,"spawn_x":2357,"spawn_y":5194,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2305,"spawn_y":5233,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2305,"spawn_y":5241,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2306,"spawn_y":5229,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2306,"spawn_y":5243,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2309,"spawn_y":5246,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2311,"spawn_y":5237,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2314,"spawn_y":5237,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2314,"spawn_y":5244,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2315,"spawn_y":5236,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2316,"spawn_y":5227,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2322,"spawn_y":5235,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Ghost_9","spawn_level":0,"spawn_x":2325,"spawn_y":5228,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1859,"spawn_y":5193,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1859,"spawn_y":5203,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5192,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5200,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5229,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1861,"spawn_y":5231,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1862,"spawn_y":5188,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1862,"spawn_y":5204,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1863,"spawn_y":5227,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1864,"spawn_y":5202,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1865,"spawn_y":5188,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1866,"spawn_y":5204,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1868,"spawn_y":5189,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1869,"spawn_y":5226,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1870,"spawn_y":5192,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1871,"spawn_y":5232,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1872,"spawn_y":5190,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1872,"spawn_y":5218,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1872,"spawn_y":5239,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1873,"spawn_y":5188,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1874,"spawn_y":5200,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1874,"spawn_y":5210,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1876,"spawn_y":5189,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1876,"spawn_y":5216,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1877,"spawn_y":5220,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1879,"spawn_y":5198,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1879,"spawn_y":5230,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1879,"spawn_y":5234,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1880,"spawn_y":5200,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1880,"spawn_y":5239,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1880,"spawn_y":5243,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1881,"spawn_y":5232,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1881,"spawn_y":5241,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1882,"spawn_y":5199,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1882,"spawn_y":5201,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1882,"spawn_y":5243,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1883,"spawn_y":5206,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1883,"spawn_y":5234,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1884,"spawn_y":5230,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1884,"spawn_y":5232,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1885,"spawn_y":5205,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1886,"spawn_y":5231,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1887,"spawn_y":5203,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1889,"spawn_y":5206,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1890,"spawn_y":5242,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1895,"spawn_y":5242,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1898,"spawn_y":5241,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1900,"spawn_y":5240,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1900,"spawn_y":5244,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1904,"spawn_y":5235,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1908,"spawn_y":5203,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1909,"spawn_y":5243,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1910,"spawn_y":5202,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1911,"spawn_y":5205,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1912,"spawn_y":5238,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Rat_16","spawn_level":0,"spawn_x":1913,"spawn_y":5235,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2142,"spawn_y":5252,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2145,"spawn_y":5252,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2145,"spawn_y":5256,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2148,"spawn_y":5253,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2149,"spawn_y":5255,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2152,"spawn_y":5251,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2153,"spawn_y":5254,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon","spawn_level":0,"spawn_x":2155,"spawn_y":5252,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2158,"spawn_y":5282,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2161,"spawn_y":5281,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2162,"spawn_y":5284,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_1","spawn_level":0,"spawn_x":2164,"spawn_y":5280,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2119,"spawn_y":5296,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2120,"spawn_y":5292,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2120,"spawn_y":5299,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2122,"spawn_y":5291,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2123,"spawn_y":5302,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2127,"spawn_y":5305,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2129,"spawn_y":5302,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2130,"spawn_y":5298,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2163,"spawn_y":5301,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2166,"spawn_y":5303,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2166,"spawn_y":5306,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2167,"spawn_y":5300,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2169,"spawn_y":5303,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Catablepon_2","spawn_level":0,"spawn_x":2170,"spawn_y":5306,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2120,"spawn_y":5275,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2121,"spawn_y":5273,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2123,"spawn_y":5270,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2123,"spawn_y":5275,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2124,"spawn_y":5272,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2124,"spawn_y":5274,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2125,"spawn_y":5270,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2126,"spawn_y":5274,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2127,"spawn_y":5272,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2129,"spawn_y":5268,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2129,"spawn_y":5270,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2130,"spawn_y":5272,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2131,"spawn_y":5267,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2142,"spawn_y":5256,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2143,"spawn_y":5251,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2145,"spawn_y":5253,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2145,"spawn_y":5306,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2146,"spawn_y":5259,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2146,"spawn_y":5306,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2147,"spawn_y":5255,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2147,"spawn_y":5305,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2147,"spawn_y":5307,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2149,"spawn_y":5304,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2149,"spawn_y":5305,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2149,"spawn_y":5306,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2150,"spawn_y":5307,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2150,"spawn_y":5308,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2151,"spawn_y":5268,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2151,"spawn_y":5305,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2151,"spawn_y":5308,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2152,"spawn_y":5268,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2152,"spawn_y":5306,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5270,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5305,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5306,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2153,"spawn_y":5307,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5269,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5271,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5273,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2154,"spawn_y":5305,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2155,"spawn_y":5254,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2155,"spawn_y":5305,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2157,"spawn_y":5271,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2157,"spawn_y":5273,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Giant spider_2","spawn_level":0,"spawn_x":2157,"spawn_y":5274,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2119,"spawn_y":5274,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2120,"spawn_y":5277,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2121,"spawn_y":5276,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2122,"spawn_y":5271,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2124,"spawn_y":5269,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2124,"spawn_y":5273,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2126,"spawn_y":5270,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2126,"spawn_y":5272,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2127,"spawn_y":5269,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2127,"spawn_y":5273,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2128,"spawn_y":5271,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2129,"spawn_y":5267,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2129,"spawn_y":5273,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2150,"spawn_y":5267,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2152,"spawn_y":5270,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2155,"spawn_y":5270,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2155,"spawn_y":5271,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2156,"spawn_y":5273,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2156,"spawn_y":5274,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2165,"spawn_y":5255,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2166,"spawn_y":5256,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2167,"spawn_y":5251,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2167,"spawn_y":5253,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2167,"spawn_y":5255,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2168,"spawn_y":5253,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2168,"spawn_y":5256,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2168,"spawn_y":5257,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2169,"spawn_y":5250,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2169,"spawn_y":5254,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2169,"spawn_y":5255,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2170,"spawn_y":5252,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2170,"spawn_y":5254,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2170,"spawn_y":5256,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2171,"spawn_y":5253,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Spider_5","spawn_level":0,"spawn_x":2172,"spawn_y":5254,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2169,"spawn_y":5290,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2170,"spawn_y":5285,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2171,"spawn_y":5280,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Scorpion_2","spawn_level":0,"spawn_x":2172,"spawn_y":5274,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2170,"spawn_y":5277,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2170,"spawn_y":5288,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2172,"spawn_y":5283,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Scorpion_3","spawn_level":0,"spawn_x":2173,"spawn_y":5272,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1862,"spawn_y":5190,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1863,"spawn_y":5189,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1867,"spawn_y":5188,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1870,"spawn_y":5235,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1871,"spawn_y":5191,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1871,"spawn_y":5242,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1872,"spawn_y":5227,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1873,"spawn_y":5212,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1873,"spawn_y":5238,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1874,"spawn_y":5216,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1875,"spawn_y":5214,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1875,"spawn_y":5218,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1878,"spawn_y":5216,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1879,"spawn_y":5218,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1880,"spawn_y":5217,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur","spawn_level":0,"spawn_x":1880,"spawn_y":5220,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1890,"spawn_y":5195,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1890,"spawn_y":5243,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5189,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5198,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5238,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1893,"spawn_y":5241,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1894,"spawn_y":5243,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1897,"spawn_y":5196,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1897,"spawn_y":5244,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1899,"spawn_y":5198,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1899,"spawn_y":5242,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1900,"spawn_y":5208,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1900,"spawn_y":5210,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5191,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5197,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5201,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1901,"spawn_y":5206,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5193,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5199,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5204,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5207,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1902,"spawn_y":5242,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Minotaur_2","spawn_level":0,"spawn_x":1904,"spawn_y":5191,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1860,"spawn_y":5205,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1860,"spawn_y":5222,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1860,"spawn_y":5226,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_47","spawn_level":0,"spawn_x":1862,"spawn_y":5201,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1859,"spawn_y":5201,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1860,"spawn_y":5215,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1862,"spawn_y":5220,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1863,"spawn_y":5215,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1863,"spawn_y":5217,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_48","spawn_level":0,"spawn_x":1864,"spawn_y":5218,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1859,"spawn_y":5219,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1861,"spawn_y":5224,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1862,"spawn_y":5228,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_49","spawn_level":0,"spawn_x":1864,"spawn_y":5204,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1884,"spawn_y":5207,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1885,"spawn_y":5203,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1891,"spawn_y":5236,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1894,"spawn_y":5229,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1910,"spawn_y":5236,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_50","spawn_level":0,"spawn_x":1913,"spawn_y":5239,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1876,"spawn_y":5198,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1884,"spawn_y":5201,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1887,"spawn_y":5205,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1893,"spawn_y":5235,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1895,"spawn_y":5228,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1896,"spawn_y":5232,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1911,"spawn_y":5240,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1912,"spawn_y":5244,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_51","spawn_level":0,"spawn_x":1913,"spawn_y":5236,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1877,"spawn_y":5199,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1892,"spawn_y":5226,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1894,"spawn_y":5233,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1905,"spawn_y":5235,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Goblin_52","spawn_level":0,"spawn_x":1912,"spawn_y":5242,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1870,"spawn_y":5226,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1871,"spawn_y":5229,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1871,"spawn_y":5236,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1872,"spawn_y":5233,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_3","spawn_level":0,"spawn_x":1873,"spawn_y":5239,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1886,"spawn_y":5188,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1887,"spawn_y":5221,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5187,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5191,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5214,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1889,"spawn_y":5217,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1890,"spawn_y":5224,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1892,"spawn_y":5217,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1892,"spawn_y":5220,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1894,"spawn_y":5193,"movement_radius": 10,"face":"WEST"} +,{"npc":"rs:Wolf_4","spawn_level":0,"spawn_x":1897,"spawn_y":5189,"movement_radius": 10,"face":"WEST"} +] diff --git a/data/config/widgets.json5 b/data/config/widgets.json5 index 6f4691631..67680cccc 100644 --- a/data/config/widgets.json5 +++ b/data/config/widgets.json5 @@ -76,6 +76,7 @@ questJournal: 275, questReward: 277, welcomeScreen: 378, + welcomeScreenConstruction: 405, welcomeScreenChildren: { cogs: 16, question: 17, @@ -87,5 +88,38 @@ christmas: 23, killcount: 24 }, - whatWouldYouLikeToSpin: 459 + whatWouldYouLikeToSpin: 459, + book: 27, + bookChildren: { + title: 3, + totalPageLineAmount: 14, + leftPage: { + pageTurnButton: 95, + clickableLines: { + firstLineId: 101, + incrementAmount: 2, + lastLine: 129 + }, + nonClickableLines: { + firstLineId: 33, + incrementAmount: 1, + lastLine: 47 + }, + pageNumber: 98 + }, + rightPage: { + pageTurnButton: 97, + clickableLines: { + firstLineId: 130, + incrementAmount: 2, + lastLine: 160 + }, + nonClickableLines: { + firstLineId: 48, + incrementAmount: 1, + lastLine: 62 + }, + pageNumber: 99 + } + } } diff --git a/src/game-engine/config/index.ts b/src/game-engine/config/index.ts index f66113c7f..916e206a2 100644 --- a/src/game-engine/config/index.ts +++ b/src/game-engine/config/index.ts @@ -22,14 +22,14 @@ import { loadMusicRegionConfigurations, MusicTrack } from '@engine/config/music- import { loadXteaRegionFiles, XteaRegion } from '@runejs/filestore'; import { loadStrongholdOfSecurityQuizData, - StrongholdOfSecurityQuestion, - StrongholdOfSecurityQuiz -} from '@plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin'; + StrongholdOfSecurityQuiz, + StrongholdOfSecurityQuizQuestion +} from '@engine/config/stronghold-of-security-quiz-config'; +import { BookData, loadStrongholdOfSecurityBookData } from '@engine/config/sectioned-book-config'; require('json5/lib/register'); - export let itemMap: { [key: string]: ItemDetails }; export let itemIdMap: { [key: number]: string }; export let itemPresetMap: ItemPresetConfiguration; @@ -43,6 +43,7 @@ export let shopMap: { [key: string]: Shop }; export let skillGuides: SkillGuide[] = []; export let xteaRegions: { [key: number]: XteaRegion }; export let strongholdOfSecurityQuizData: StrongholdOfSecurityQuiz; +export let strongholdOfSecurityBookData: BookData; export const musicRegionMap = new Map(); export const widgets: { [key: string]: any } = require('../../../data/config/widgets.json5'); @@ -65,6 +66,7 @@ export async function loadGameConfigurations(): Promise { npcPresetMap = npcPresets; strongholdOfSecurityQuizData = await loadStrongholdOfSecurityQuizData(`data/config/stronghold-of-security-quiz.json5`); + strongholdOfSecurityBookData = await loadStrongholdOfSecurityBookData(`data/config/books/security-book.json5`); npcSpawns = await loadNpcSpawnConfigurations('data/config/npc-spawns/'); musicRegions = await loadMusicRegionConfigurations(); musicRegions.forEach(song => song.regionIds.forEach(region => musicRegionMap.set(region, song.songId))); @@ -72,55 +74,55 @@ export async function loadGameConfigurations(): Promise { shopMap = await loadShopConfigurations('data/config/shops/'); skillGuides = await loadSkillGuideConfigurations('data/config/skill-guides/'); - logger.info(`Loaded ${strongholdOfSecurityQuizData} Stronghold of Security questions.`); + logger.info(`Loaded ${strongholdOfSecurityQuizData.questions.length} Stronghold of Security questions.`); logger.info(`Loaded ${musicRegions.length} music regions, ${Object.keys(itemMap).length} items, ${itemSpawns.length} item spawns, ` + `${Object.keys(npcMap).length} npcs, ${npcSpawns.length} npc spawns, ${Object.keys(shopMap).length} shops and ${skillGuides.length} skill guides.`); } export const findItem = (itemKey: number | string): ItemDetails | null => { - if(!itemKey) { + if (!itemKey) { return null; } let gameId: number; - if(typeof itemKey === 'number') { + if (typeof itemKey === 'number') { gameId = itemKey; itemKey = itemIdMap[gameId]; - if(!itemKey) { + if (!itemKey) { logger.warn(`Item ${gameId} is not yet registered on the server.`); } } let item; - if(itemKey) { + if (itemKey) { item = itemMap[itemKey]; - if(!item) { + if (!item) { // Try fetching variation with suffix 0 item = itemMap[`${itemKey}:0`] } - if(item?.gameId) { + if (item?.gameId) { gameId = item.gameId; } - if(item?.extends) { + if (item?.extends) { let extensions = item.extends; - if(typeof extensions === 'string') { - extensions = [ extensions ]; + if (typeof extensions === 'string') { + extensions = [extensions]; } extensions.forEach(extKey => { const extensionItem = itemPresetMap[extKey]; - if(extensionItem) { + if (extensionItem) { item = _.merge(item, translateItemConfig(undefined, extensionItem)); } }); } } - if(gameId) { + if (gameId) { const cacheItem = filestore.configStore.itemStore.getItem(gameId); item = _.merge(item, cacheItem); } @@ -130,17 +132,17 @@ export const findItem = (itemKey: number | string): ItemDetails | null => { export const findNpc = (npcKey: number | string): NpcDetails | null => { - if(!npcKey) { + if (!npcKey) { return null; } - if(typeof npcKey === 'number') { + if (typeof npcKey === 'number') { const gameId = npcKey; npcKey = npcIdMap[gameId]; - if(!npcKey) { + if (!npcKey) { const cacheNpc = filestore.configStore.npcStore.getNpc(gameId); - if(cacheNpc) { + if (cacheNpc) { return cacheNpc as any; } else { logger.warn(`NPC ${gameId} is not yet configured on the server and a matching cache NPC was not found.`); @@ -150,25 +152,25 @@ export const findNpc = (npcKey: number | string): NpcDetails | null => { } let npc = npcMap[npcKey]; - if(!npc) { + if (!npc) { // Try fetching variation with suffix 0 npc = npcMap[`${npc}:0`] } - if(!npc) { + if (!npc) { logger.warn(`NPC ${npcKey} is not yet configured on the server and a matching cache NPC was not provided.`); return null; } - if(npc.extends) { + if (npc.extends) { let extensions = npc.extends; - if(typeof extensions === 'string') { - extensions = [ extensions ]; + if (typeof extensions === 'string') { + extensions = [extensions]; } extensions.forEach(extKey => { const extensionNpc = npcPresetMap[extKey]; - if(extensionNpc) { + if (extensionNpc) { npc = _.merge(npc, translateNpcServerConfig(undefined, extensionNpc)); } }); @@ -179,7 +181,7 @@ export const findNpc = (npcKey: number | string): NpcDetails | null => { export const findShop = (shopKey: string): Shop | null => { - if(!shopKey) { + if (!shopKey) { return null; } @@ -203,7 +205,7 @@ export const findSongIdByRegionId = (regionId: number): number | null => { return musicRegionMap.has(regionId) ? musicRegionMap.get(regionId) : null; }; -export function getRandomStrongholdOfSecurityQuestion(): StrongholdOfSecurityQuestion | null { +export function getRandomStrongholdOfSecurityQuizQuestion(): StrongholdOfSecurityQuizQuestion | null { const randomIndex = Math.floor(Math.random() * strongholdOfSecurityQuizData.questions.length); return strongholdOfSecurityQuizData.questions[randomIndex]; } diff --git a/src/game-engine/config/sectioned-book-config.ts b/src/game-engine/config/sectioned-book-config.ts new file mode 100644 index 000000000..a4ca2e03a --- /dev/null +++ b/src/game-engine/config/sectioned-book-config.ts @@ -0,0 +1,148 @@ +import { JSON_SCHEMA, safeLoad } from 'js-yaml'; +import { readFileSync } from 'fs'; +import { logger } from '@runejs/core'; +import { filestore } from '@engine/game-server'; +import { TextWidget } from '@runejs/filestore'; +import { wrapText } from '@engine/util/strings'; + + +export interface BookData { + sectionLocations: { [key: string]: number }; + bookPages: { [key: number]: BookPage }; + bookContents: BookContents; +} + +/** + * The contents of a Book, represented by an array of BookSections, an associated item ID, and book title. + */ +export interface BookContents { + bookId: number; + bookTitle: string; + showTableOfContents: boolean; + bookSections: BookSections[]; +} + +/** + * A book section that contains a title, and text. + */ +export interface BookSections { + header: string; + text: string; +} + +/** + * A BookPage represents a single page of a book. + */ +export interface BookPage { + header?: string; + lines: string[]; +} + +export function bookSectionHeaderExists(bookContents: BookContents, bookSectionHeader: string): boolean { + for (const bookSection of bookContents.bookSections) { + if (bookSection.header === bookSectionHeader) { + return true; + } + } + return false; +} + +function getPageDataFromPageNumber(bookContents: BookContents, page: number): BookPage { + const textWidget = filestore.widgetStore.decodeWidget(215) as TextWidget; + + const output = []; + if (page === 1 && bookContents.showTableOfContents) { + output.push(``); + bookContents.bookSections.forEach(section => output.push(section.header)); + return { header: `Chapters`, lines: output }; + } + + const outputPages: BookPage[] = []; + bookContents.bookSections.forEach(section => { + const wrappedText = wrapText(section.text, 202, textWidget.fontId); + let pageLineAmount = 14; + + for (let line = 0; line < wrappedText.length; line += pageLineAmount) { + if ((pageLineAmount + line) > (wrappedText.length - line)) { + pageLineAmount = wrappedText.length - line; + } + if (line === 0) { + outputPages.push({ header: section.header, lines: wrappedText.slice(line, line + pageLineAmount) }); + } else { + outputPages.push({ lines: wrappedText.slice(line, line + pageLineAmount) }); + } + } + }); + + if(outputPages[page - 2] === undefined) { + return undefined; + } + return outputPages[page - 2]; +} + +export const pageExists = (book: BookContents, page: number): boolean => { + return book.bookSections[page - 1] !== undefined; +} +function getBookPagesFromBookContents(bookContents: BookContents): { [key: number]: BookPage } { + const bookPages: { [key: number]: BookPage } = {}; + + let pageNumber = 1; + let bookPage = getPageDataFromPageNumber(bookContents, pageNumber); + while (bookPage !== undefined) { + bookPages[pageNumber] = bookPage; + pageNumber++; + bookPage = getPageDataFromPageNumber(bookContents, pageNumber); + } + return bookPages; +} + + +function getSectionLocationsFromBookContents(bookContents: BookContents): { [key: string]: number } { + const sectionLocations: { [key: string]: number } = {}; + // const bookPages: { [key: number]: BookPage } = {}; + let pageNumber = 1; + let leftPageData = getPageDataFromPageNumber(bookContents, (2 * pageNumber) - 1); + let rightPageData = getPageDataFromPageNumber(bookContents, (2 * pageNumber)); + + while (leftPageData.lines !== undefined && rightPageData.lines !== undefined) { + + const leftBookPage = (2 * pageNumber) - 1; + const rightBookPage = (2 * pageNumber) - 1; + leftPageData = getPageDataFromPageNumber(bookContents, leftBookPage); + rightPageData = getPageDataFromPageNumber(bookContents, rightBookPage); + + if(leftPageData === undefined) { + return; + } + if(rightPageData === undefined) { + return; + } + if (leftPageData.header) { + sectionLocations[leftPageData.header] = leftBookPage; + } + if (rightPageData.header) { + sectionLocations[rightPageData.header] = rightBookPage; + } + pageNumber += 1; + + + } + return sectionLocations; +} + +export function loadStrongholdOfSecurityBookData(path: string): BookData | null { + try { + const book = safeLoad(readFileSync(path, 'utf8'), + { schema: JSON_SCHEMA }) as BookContents; + + if (!book) { + throw new Error('Unable to read book data!'); + } + + const sectionLocations = getSectionLocationsFromBookContents(book); + const bookPages = getBookPagesFromBookContents(book); + return { bookPages: bookPages, bookContents: book, sectionLocations: sectionLocations }; + } catch (error) { + logger.error('Error parsing book data: ' + error); + } +} diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts b/src/game-engine/config/stronghold-of-security-quiz-config.ts similarity index 67% rename from src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts rename to src/game-engine/config/stronghold-of-security-quiz-config.ts index f3a793703..c8b96f2a0 100644 --- a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin.ts +++ b/src/game-engine/config/stronghold-of-security-quiz-config.ts @@ -1,17 +1,29 @@ +import { loadConfigurationFiles } from '@runejs/core/fs'; +import { itemMap } from '@engine/config/index'; +import { ItemDetails } from '@engine/config/item-config'; import { JSON_SCHEMA, safeLoad } from 'js-yaml'; import { readFileSync } from 'fs'; import { logger } from '@runejs/core'; +/** + * Stronghold of Security quiz configuration + */ export interface StrongholdOfSecurityQuiz { prefix: string; - questions: StrongholdOfSecurityQuestion[]; + questions: StrongholdOfSecurityQuizQuestion[]; } -export interface StrongholdOfSecurityQuestion { +/** + * Stronghold of Security question + */ +export interface StrongholdOfSecurityQuizQuestion { questionText: string; options: StrongholdQuizOption[]; } +/** + * Stronghold of Security quiz option + */ export interface StrongholdQuizOption { optionText: string; passable: boolean; @@ -31,6 +43,3 @@ export function loadStrongholdOfSecurityQuizData(path: string): StrongholdOfSecu logger.error('Error parsing stronghold of security quiz data: ' + error); } } -export default { - pluginId: 'rs:stronghold_of_security_quiz', -}; diff --git a/src/game-engine/world/config/item-ids.ts b/src/game-engine/world/config/item-ids.ts index 21bbd485b..d24d697d7 100644 --- a/src/game-engine/world/config/item-ids.ts +++ b/src/game-engine/world/config/item-ids.ts @@ -408,5 +408,10 @@ export const itemIds = { }, grappleTips: { mithril: 9415 + }, + dungeons: { + strongholdOfSecurity: { + strongholdNotes: 9004, + } } }; diff --git a/src/plugins/buttons/player-emotes.plugin.ts b/src/plugins/buttons/player-emotes.plugin.ts index 11cd1b4a6..426a78416 100644 --- a/src/plugins/buttons/player-emotes.plugin.ts +++ b/src/plugins/buttons/player-emotes.plugin.ts @@ -173,7 +173,7 @@ export const handler: buttonActionHandler = (details) => { return; } } - + player.interfaceState.closeAllSlots(); player.playAnimation(emote.animationId); if(emote.graphicId !== undefined) { diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-book.plugin.ts b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-book.plugin.ts new file mode 100644 index 000000000..51740b123 --- /dev/null +++ b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-book.plugin.ts @@ -0,0 +1,348 @@ +import { ItemInteractionAction, itemInteractionActionHandler } from '@engine/world/action/item-interaction.action'; +import { strongholdOfSecurityBookData, widgets } from '@engine/config'; +import { TaskExecutor } from '@engine/world/action'; +import { widgetInteractionActionHandler } from '@engine/world/action/widget-interaction.action'; +import { Player } from '@engine/world/actor/player/player'; +import { BookData, bookSectionHeaderExists, BookSections, pageExists } from '@engine/config/sectioned-book-config'; +import { Widget, WidgetClosedEvent } from '@engine/world/actor/player/interface-state'; + +/** + * Open the book interface and read the specified book. + * + * @param details Information about the action. + */ +export const activate: itemInteractionActionHandler = (details) => { + details.player.playAnimation(1350); + openBook(details.player, strongholdOfSecurityBookData, 1); + details.player.metadata['readingBook'] = details.player.interfaceState.closed.subscribe((whatClosed: WidgetClosedEvent) => { + if (whatClosed && whatClosed.widget && whatClosed.widget.widgetId === widgets.book) { + details.player.stopGraphics(); + details.player.stopAnimation(); + } + }); +}; + +/** + * Given book information, and a specified header from that book, return the page number that it's on. + * @param bookData The book with the section header you want to find. + * @param bookSectionHeader The name of the section to find the page of. + */ +function getPageNumberForBookSection(bookData: BookData, bookSectionHeader: string): number { + let pageNumber = 1; + let leftPageNumber = (2 * pageNumber) - 1; + let rightPageNumber = 2 * pageNumber; + + let leftPageData = bookData.bookPages[leftPageNumber]; + let rightPageData = bookData.bookPages[rightPageNumber]; + while (leftPageData.lines !== undefined && rightPageData.lines !== undefined) { + if (leftPageData.header === bookSectionHeader) { + return pageNumber; + } else if (rightPageData.header === bookSectionHeader) { + return pageNumber; + } + pageNumber += 1; + leftPageNumber = (2 * pageNumber) - 1; + rightPageNumber = (2 * pageNumber); + leftPageData = bookData.bookPages[leftPageNumber]; + rightPageData = bookData.bookPages[rightPageNumber]; + } + return 1; +} + +/** + * Toggles the visibility of clickable text widgets on the left page, given a range of line numbers. + * + * This is used for the table of contents, allowing only selectable sections to be clickable. + * + * @param player The player who's book widget will be modified. + * @param widget The widget being modified. + * @param hidden Whether to hide or unhide the particular clickable widget line + * @param fromLine The line number to begin with. + * @param toLine The line number to end on. + */ +function toggleVisibilityOfLeftPageClickableWidgets(player: Player, widget: Widget, hidden: boolean, fromLine?: number, toLine?: number) { + if (toLine === undefined) { + toLine = 15; + } + + if (fromLine === undefined) { + fromLine = 0; + } + for (let i = fromLine; i <= toLine; i++) { + player.modifyWidget(widget.widgetId, { + childId: 100 + (2 * i), + hidden: hidden, + text: `` + }); + } +} + +/** + * Clears the book interface to prepare it for new data. + * @param player The player who's book interface will be cleared. + * @param widget The book widget being modified. + */ +function clearBookInterface(player: Player, widget: Widget) { + let clickable = true; + for (let interfaceTextType = 0; interfaceTextType < 2; interfaceTextType++) { + for (const pageSide of Object.values(PageSide)) { + for (let lineNumber = 0; lineNumber <= widgets.bookChildren.totalPageLineAmount; lineNumber++) { + const childId = getLineChildId(pageSide as PageSide, clickable, lineNumber); + player.modifyWidget(widget.widgetId, { + childId: childId, + hidden: true, + text: `` + }); + } + } + clickable = !clickable; + } + + //Hide the right page turn button + player.modifyWidget(widget.widgetId, { + childId: widgets.bookChildren.rightPage.pageTurnButton, + hidden: true + }); + + //Hide the left page turn button + player.modifyWidget(widget.widgetId, { + childId: widgets.bookChildren.rightPage.pageTurnButton, + hidden: true + }); +} + +/** + * Creates the book widget from the specified Book object, and populates it according to what page you're on. + * @param player The player who will view the book. + * @param bookData The book the player will view. + * @param page The page-set that will be viewed. (This number accounts for two pages at a time. Page 1 and 2 would be 1. + * Pages 3 and 4 would be 2, etc.) + */ +export function openBook(player: Player, bookData: BookData, page: number): void { + const widget = player.interfaceState.openWidget(widgets.book, { + slot: 'screen', + fakeWidget: 3100003, + metadata: { + page: page + } + }); + + clearBookInterface(player, widget); + + toggleVisibilityOfLeftPageClickableWidgets(player, widget, true); + + const leftPageNumber = (2 * page) - 1; + const leftPage = bookData.bookPages[leftPageNumber]; + if (!leftPage) { + return; + } + + addBookTextToWidget(player, widget, bookData, leftPageNumber, PageSide.LEFT_SIDE); + + const rightPageNumber = (2 * page); + const rightPage = bookData.bookPages[rightPageNumber]; + if (rightPage) { + addBookTextToWidget(player, widget, bookData, rightPageNumber, PageSide.RIGHT_SIDE); + } + + addPageNumberToBookWidget(player, widget); +} + +/** + * Modifies the specified book widget to add page numbers according to the widget's metadata. + * @param player The player who's widget is being modified. + * @param widget The widget to modify with new page numbers. + */ +function addPageNumberToBookWidget(player: Player, widget: Widget) { + player.modifyWidget(widget.widgetId, { + childId: widgets.bookChildren.rightPage.pageNumber, + text: `Page ${widget.metadata.page * 2}` + }); + player.modifyWidget(widget.widgetId, { + childId: widgets.bookChildren.leftPage.pageNumber, + text: `Page ${widget.metadata.page * 2 - 1}` + }); +} + + +/** + * Given a BookData object, and information about where the book data should be applied to, add the appropriate book text + * to the specified book widget. + * @param player The player who's widget is being modified. + * @param widget The widget to modify with text from the book. + * @param book The book to get the text from. + * @param pageNumber The page to apply the text onto. + * @param side Whether or not the page is on the left or right side. + */ +function addBookTextToWidget(player: Player, widget: Widget, book: BookData, pageNumber: number, side: PageSide) { + const page = book.bookPages[pageNumber]; + const clickable = (book.bookContents.showTableOfContents && pageNumber === 1); + const totalPageLines = widgets.bookChildren.totalPageLineAmount; + + const hideLeftPageTurn: boolean = (pageNumber === 1 || pageNumber === 2); + player.modifyWidget(widget.widgetId, { + childId: 95, + hidden: hideLeftPageTurn + }); + + player.modifyWidget(widget.widgetId, { + childId: 97, + hidden: (pageNumber === Object.keys(book.bookPages).length) + }); + let childId; + let lineText; + + for (let lineNumber = 0; lineNumber <= totalPageLines; lineNumber++) { + childId = getLineChildId(side, clickable, lineNumber); + + if (page.header && lineNumber === 0) { + childId = getLineChildId(side, false, lineNumber); + lineText = `` + page.header; + } else if (page.header && lineNumber !== 0) { + lineText = page.lines[lineNumber - 1]; + } else if (!page.header) { + lineText = page.lines[lineNumber]; + } + player.modifyWidget(widget.widgetId, { + childId: childId, + text: lineText + }); + } + + if (side === PageSide.LEFT_SIDE) { + if (pageNumber === 1 && book.bookContents.showTableOfContents) { + const bookSections = book.bookContents.bookSections.length + 1; + toggleVisibilityOfLeftPageClickableWidgets(player, widget, false, 2, bookSections); + } + } +} + +/** + * Given which side the page is on, and whether or not the line should be clickable, return widget information about the + * page. + * @param pageSide Whether the page is on the left or right side of the book. + * @param clickable Whether or not the line should be clickable. + */ +function getWidgetInformationForPage(pageSide: PageSide, clickable: boolean) { + let lineType; + + switch (pageSide) { + case PageSide.RIGHT_SIDE: + lineType = (clickable ? widgets.bookChildren.rightPage.clickableLines : widgets.bookChildren.rightPage.nonClickableLines) + break; + + case PageSide.LEFT_SIDE: + lineType = (clickable ? widgets.bookChildren.leftPage.clickableLines : widgets.bookChildren.leftPage.nonClickableLines) + break; + } + return { firstLineId: lineType.firstLineId, incrementAmount: lineType.incrementAmount }; +} + +/** + * Given which side the page is on, whether or not the line should be clickable, and the line number, return the + * appropriate child ID for the book interface. + * + * @param pageSide Whether the page is on the left or right side of the book. + * @param clickable Whether or not the line should be clickable. + * @param lineNumber Which particular line you want to get the child ID of. + */ +function getLineChildId(pageSide: PageSide, clickable: boolean, lineNumber: number): number { + const pageWidgetData: { firstLineId: number, incrementAmount: number } = getWidgetInformationForPage(pageSide, clickable); + + return pageWidgetData.firstLineId + (lineNumber * pageWidgetData.incrementAmount); +} + +/** + * An enum that represents either the left, or the right page in a book. + */ +enum PageSide { + LEFT_SIDE = 'LEFT', + RIGHT_SIDE = 'RIGHT' +} + +/** + * Handles interactions with the book interface itself, such as using the left and right buttons + * @param details + */ +export const strongholdBookInteract: widgetInteractionActionHandler = (details) => { + const playerWidget = details.player.interfaceState.findWidget(27); + + if (!playerWidget || !playerWidget.metadata.page || playerWidget.fakeWidget !== 3100003) { + return; + } + const bookData: BookData = strongholdOfSecurityBookData; + + + let pageNumber = playerWidget.metadata.page; + switch (details.childId) { + case 160: + openBook(details.player, bookData, 1); + return; + case 94: + pageNumber--; + if (pageExists(bookData.bookContents, pageNumber)) { + details.player.playAnimation(3141); + openBook(details.player, bookData, pageNumber); + } else { + openBook(details.player, bookData, pageNumber + 1); + } + return; + case 96: + pageNumber++; + if (pageExists(bookData.bookContents, pageNumber)) { + details.player.playAnimation(3140); + openBook(details.player, bookData, pageNumber); + } else { + openBook(details.player, bookData, pageNumber - 1); + } + return; + } + + + let selectedIndex = undefined; + if (details.childId >= 101 && details.childId <= 129) { + selectedIndex = (details.childId - 99) / 2 - 1; + } + if (details.childId >= 131 && details.childId <= 159) { + selectedIndex = ((details.childId - 129) / 2 - 1) + 15; + } + if (selectedIndex !== undefined) { + const bookSection: BookSections = bookData.bookContents.bookSections[selectedIndex - 2]; + + if (bookSectionHeaderExists(bookData.bookContents, bookSection.header)) { + const sectionName = bookSection.header; + const selectedPage = getPageNumberForBookSection(bookData, sectionName); + openBook(details.player, bookData, selectedPage); + } else { + openBook(details.player, bookData, pageNumber); + } + } +} + + +const canActivate = (task: TaskExecutor, taskIteration: number): boolean => { + return true; +} + +const onComplete = (task: TaskExecutor): void => { + task.actor.stopAnimation(); + task.actor.stopGraphics(); +} + +export default { + pluginId: 'rs:stronghold_of_security_book', + hooks: [ + { + type: 'item_interaction', + widgets: widgets.inventory, + options: 'read', + handler: activate, + cancelOtherActions: true + }, + { + type: 'widget_interaction', + widgetId: 3100003, + handler: strongholdBookInteract + } + ] +}; diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security.plugin.ts b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security.plugin.ts index 05c5d8c44..4145491c9 100644 --- a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security.plugin.ts +++ b/src/plugins/dungeons/stronghold-of-security/stronghold-of-security.plugin.ts @@ -4,15 +4,15 @@ import { directionNameFromIndex, WNES } from '@engine/world/direction'; import { schedule } from '@engine/world/task'; import { dialogue, DialogueTree, Emote, execute } from '@engine/world/actor/dialogue'; import { Position } from '@engine/world/position'; -import { getRandomStrongholdOfSecurityQuestion, strongholdOfSecurityQuizData } from '@engine/config'; +import { getRandomStrongholdOfSecurityQuizQuestion, strongholdOfSecurityQuizData } from '@engine/config'; import { Player } from '@engine/world/actor/player/player'; -import { StrongholdOfSecurityQuestion } from '@plugins/dungeons/stronghold-of-security/stronghold-of-security-quiz.plugin'; import { objectIds } from '@engine/world/config/object-ids'; import { getFloorCompletionFromObjectId, getNpcKeyFromObjectId } from '@plugins/dungeons/stronghold-of-security/stronghold-of-security-rewards.plugin'; import { animationIds } from '@engine/world/config/animation-ids'; +import { StrongholdOfSecurityQuizQuestion } from '@engine/config/stronghold-of-security-quiz-config'; const canActivate = (task: TaskExecutor, taskIteration: number): boolean => { const { actor, actionData: { position, object } } = task; @@ -118,7 +118,7 @@ async function promptPlayerWithSecurityQuestion(player: Player, questionAttempt: await dialogue([player, { npc: npcKey, key: 'gate' - }], generateStrongholdQuizDialogue(player, getRandomStrongholdOfSecurityQuestion())); + }], generateStrongholdQuizDialogue(player, getRandomStrongholdOfSecurityQuizQuestion())); if (player.sessionMetadata[`correctAnswer`]) { return true; @@ -139,7 +139,7 @@ const isWelcomeDoor = (objectPosition: Position): boolean => { return (objectPosition.equals(leftWelcomeDoor) || objectPosition.equals(rightWelcomeDoor)); }; -function generateStrongholdQuizDialogue(player: Player, strongholdQuestion: StrongholdOfSecurityQuestion): DialogueTree { +function generateStrongholdQuizDialogue(player: Player, strongholdQuestion: StrongholdOfSecurityQuizQuestion): DialogueTree { const questionText = strongholdOfSecurityQuizData.prefix + strongholdQuestion.questionText; player.sessionMetadata[`strongholdDialogueComplete`] = false; //TODO: Learn how to create option DialogueTrees, and make this more efficient. diff --git a/src/plugins/items/rotten-potato/helpers/rotten-potato-travel.ts b/src/plugins/items/rotten-potato/helpers/rotten-potato-travel.ts index 7685f08c9..3216e09f2 100644 --- a/src/plugins/items/rotten-potato/helpers/rotten-potato-travel.ts +++ b/src/plugins/items/rotten-potato/helpers/rotten-potato-travel.ts @@ -3,9 +3,10 @@ import { world } from '@engine/game-server'; import { widgetInteractionActionHandler } from '@engine/world/action/widget-interaction.action'; +import { widgets } from '@engine/config'; export function openTravel(player: Player, page: number) { - const widget = player.interfaceState.openWidget(27, { + const widget = player.interfaceState.openWidget(widgets.book, { slot: 'screen', fakeWidget: 3100002, metadata: { From 2586832bc963c87e510ebff4f86c82001638e953 Mon Sep 17 00:00:00 2001 From: Spencer Carlson Date: Mon, 24 May 2021 12:18:52 -0700 Subject: [PATCH 6/7] Fix: A bug where books with long sections wouldn't display all pages. A typo in a mining message Add: A method to find BookData from book ID. Book data for the Construction guide book Book data for the pie recipe book Modify: The book system to load all books from the books directory The book system to be more generic, and much simpler in certain areas. Removed lots of unnecessary duplicate calls. Rename: stronghold-of-security-book.plugin to books.plugin loadStrongholdOfSecurityBook() to loadBookData() --- data/config/books/construction-guide.json5 | 23 +++ data/config/books/pie-recipe-book.json5 | 43 ++++++ src/game-engine/config/index.ts | 22 ++- .../config/sectioned-book-config.ts | 135 +++++++----------- .../world/skill-util/harvest-skill.ts | 2 +- .../books.plugin.ts} | 130 ++++++++++------- 6 files changed, 214 insertions(+), 141 deletions(-) create mode 100644 data/config/books/construction-guide.json5 create mode 100644 data/config/books/pie-recipe-book.json5 rename src/plugins/{dungeons/stronghold-of-security/stronghold-of-security-book.plugin.ts => books/books.plugin.ts} (79%) diff --git a/data/config/books/construction-guide.json5 b/data/config/books/construction-guide.json5 new file mode 100644 index 000000000..02468abb2 --- /dev/null +++ b/data/config/books/construction-guide.json5 @@ -0,0 +1,23 @@ +{ + bookId: 8463, + bookTitle: "Guide to Construction", + showTableOfContents: true, + bookSections: [ + { + header: "How to build in your house", + text: "In order to build you will need to turn building mode on. This can be done on entering the house or using a button on the options interface. If you have a bank PIN you must enter it when entering building mode.\n\nIn building mode the ghostly shapes of furniture and doorways you have not built yet will appear in your house. These are called hotspots. You can use these to build furniture and new rooms.\n\nTo build a piece of furniture, right-click the hotspot and select Build. You will then be able to select the piece of furniture you want to build from the menu. Below each furniture icon is a list of materials; to build the furniture you will need to have all these materials in your intentory. You will also need to have a hammer, a saw and have the correct Construction level.\n\nNails work slightly differently to other materials. You will sometimes find you break nails, especially if you have a low Construction level, so you may need to bring more nails than the furniture requires. Nails made of stronger metals will break less often.\n\nYou can remove a piece of furniture if you wish to build something else in the same space. To do this, right-click on it and select Remove. You will not get any of the materials back. Some pieces of furniture can be upgraded to better pieces of furniture without having to remove them first.\n\nTo build a new room, you must use one of the door hotspots at the edges of rooms or garden squares. Right-click on it and select Build. This will bring up a list of rooms. Different rooms cost different amounts of gold and have different Construction level requirements.\n\nIf you select Build on a door hotspot that already leads to a room, you will be asked whether you want to delete that room." + }, + { + header: "Raw Materials", + text: "The main raw material you will use to make furniture is planks. The sawmill operator north east of Varrock will turn logs into planks for you, for a fee. The useful planks are wood, oak, teak and mahogany. The sawmill operator also sells saws, cloth and nails.\n\nFor higher level furniture you may also need limestone, marble, gold leaf and magic building crystals. These are sold by the stonemason who lives in Keldagrim.\n\nSome pieces of furniture also require materials that are not specific to construction, such as steel bars and soft clay.", + }, + { + header: "Room types", + text: "Parlour: This is the lowest-level room and provides space for three people to sit around a fire\nGarden: The garden is largely decorative but it also contains the exit portal.\nKitchen: This room can be used for preparing food. As you build better furniture in it you will find yourself able to prepare better meals in it.\nDining room: Eight people can sit around the tables you build in this room.\nBedroom: Some of the furniture in this room can be used to change your hair and clothes. You will also need to have two of these rooms in order to hire a servant.\nHalls: These are primarily used to connect other rooms, but they also provide space to show off the owner's skill and quest achievements.\nGames room: Various games can be built in this room to allow friends to play and train together.\nCombat Room: With this room you can challenge your friends in a personal duelling ring.\nWorkshop: This room allows you to train Construction without modifying your own house, by making furniture that can be sold to other players. It also provides space for you to train Crafting and Smithing.\nChapel: This room can be dedicated to any of RuneScape's major gods, and the altar can be used to offer bones.\nMenagerie: You can keep your pets in this room.\nStudy: You can use the lectern in this room to create clay tablets recording magic spells. Eagle lecterns make teleport spells and demon lecterns make enchantment spells. The elemental sphere in this room can be used to change the element of an elemental staff.\nPortal chamber: In this room you can build portals to various places around the world.\nFormal garden: The formal garden can contain various plants and ornaments to beautify the grounds of your house.\nThrone Room: This room can be used to hold audiences with large numbers of friends. It also contains the lever that turns on challenge mode.\nOubliette: If you build an oubliette below your throne room you can drop people from there into a cage which you can fill with various horrors.\nDungeon: Dungeon corridors and junctions can be built to create an underground maze full of monsters, traps and doors.\nTreasure room: You can place a prize in this room for visitors to your dungeon to try to reach." + }, + { + header: "Servants", + text: "Once you have two bedrooms, you can hire a servant by going to the Servants' Guild in Ardougne. You will have to pay when you hire them, and the servant will then periodically demand wages. Servants can take items to and from the bank for you, and can also greet guests and serve food and drinks." + } + ] +} diff --git a/data/config/books/pie-recipe-book.json5 b/data/config/books/pie-recipe-book.json5 new file mode 100644 index 000000000..11b10b44d --- /dev/null +++ b/data/config/books/pie-recipe-book.json5 @@ -0,0 +1,43 @@ +{ + bookId: 7162, + bookTitle: "Pie recipe book", + showTableOfContents: true, + bookSections: [ + { + header: "Redberry pie", + text: "Pour a hand full of Redberries into an empty Pie Shell, bake until the berries are soft and serve warm." + }, + { + header: "Meat pie", + text: "Line a fresh Pie Shell with Cooked Meat and heat until the pastry starts to bronze, serve with a selection of sauces." + }, + { + header: "Mud pie", + text: "Start with a Pie Shell and add a Bucket of Compost, then pour in a Bucket of Water to keep the consistency gooey. To finish, cover with Clay and bake until a good shell forms. Serve at maximum speed a good over-arm throw!" + }, + { + header: "Apple pie", + text: "Take a Pie Shell and layer in Apple, cook until the juices start to bubble and leave to cool before serving." + }, + { + header: "Garden pie", + text: "Fill a Pie Shell with Tomato, then add Onion and top with Cabbage. Bake golden brown and serve with a steak." + }, + { + header: "Fish pie", + text: "Take one Pie Shell and fill with trout, add a Cod for flavour, and then top with Potato for texture. Cook well until the potato turns golden and serve." + }, + { + header: "Admiral pie", + text: "For a more upperclass fish pie, fill your Pie Shell with Salmon and then add Tuna for colour. Top with Potato and cook until golden." + }, + { + header: "Wild pie", + text: "Line a Pie Shell with raw Bear Meat, then add Raw Chompy for substance, and top with fresh Rabbit Meat. Bake until the juices start to bubble and serve." + }, + { + header: "Summer pie", + text: "Into a Pie Shell, put Strawberry, then a layer of Watermelon, and top with Apple. Cook well and leave to cool before serving." + } + ] +} diff --git a/src/game-engine/config/index.ts b/src/game-engine/config/index.ts index d1fd2846d..670370267 100644 --- a/src/game-engine/config/index.ts +++ b/src/game-engine/config/index.ts @@ -25,8 +25,8 @@ import { StrongholdOfSecurityQuiz, StrongholdOfSecurityQuizQuestion } from '@engine/config/stronghold-of-security-quiz-config'; -import { BookData, loadStrongholdOfSecurityBookData } from '@engine/config/sectioned-book-config'; -import { LandscapeObject, loadXteaRegionFiles, ObjectConfig, XteaRegion } from '@runejs/filestore'; +import { BookData, loadBookData } from '@engine/config/sectioned-book-config'; +import { loadXteaRegionFiles, ObjectConfig, XteaRegion } from '@runejs/filestore'; require('json5/lib/register'); @@ -45,7 +45,7 @@ export let shopMap: { [key: string]: Shop }; export let skillGuides: SkillGuide[] = []; export let xteaRegions: { [key: number]: XteaRegion }; export let strongholdOfSecurityQuizData: StrongholdOfSecurityQuiz; -export let strongholdOfSecurityBookData: BookData; +export let bookData: BookData[]; export const musicRegionMap = new Map(); export const widgets: { [key: string]: any } = require('../../../data/config/widgets.json5'); @@ -68,7 +68,7 @@ export async function loadGameConfigurations(): Promise { npcPresetMap = npcPresets; strongholdOfSecurityQuizData = await loadStrongholdOfSecurityQuizData(`data/config/stronghold-of-security-quiz.json5`); - strongholdOfSecurityBookData = await loadStrongholdOfSecurityBookData(`data/config/books/security-book.json5`); + bookData = await loadBookData(`data/config/books/`); npcSpawns = await loadNpcSpawnConfigurations('data/config/npc-spawns/'); musicRegions = await loadMusicRegionConfigurations(); musicRegions.forEach(song => song.regionIds.forEach(region => musicRegionMap.set(region, song.songId))); @@ -83,7 +83,6 @@ export async function loadGameConfigurations(): Promise { `${Object.keys(npcMap).length} npcs, ${npcSpawns.length} npc spawns, ${Object.keys(shopMap).length} shops and ${skillGuides.length} skill guides.`); } - export const findItem = (itemKey: number | string): ItemDetails | null => { if (!itemKey) { return null; @@ -228,3 +227,16 @@ export function getRandomStrongholdOfSecurityQuizQuestion(): StrongholdOfSecurit const randomIndex = Math.floor(Math.random() * strongholdOfSecurityQuizData.questions.length); return strongholdOfSecurityQuizData.questions[randomIndex]; } + +export function getBookFromId(bookId: number): BookData | null { + const bookExists = bookData.some(book => book.bookContents.bookId === bookId); + if(bookExists) { + for (const book of bookData) { + if(book.bookContents.bookId === bookId) { + return book; + } + } + } else { + return null; + } +} diff --git a/src/game-engine/config/sectioned-book-config.ts b/src/game-engine/config/sectioned-book-config.ts index a4ca2e03a..fd7c100ee 100644 --- a/src/game-engine/config/sectioned-book-config.ts +++ b/src/game-engine/config/sectioned-book-config.ts @@ -4,6 +4,8 @@ import { logger } from '@runejs/core'; import { filestore } from '@engine/game-server'; import { TextWidget } from '@runejs/filestore'; import { wrapText } from '@engine/util/strings'; +import { loadConfigurationFiles } from '@runejs/core/fs'; +import * as fs from 'fs'; export interface BookData { @@ -36,113 +38,82 @@ export interface BookSections { export interface BookPage { header?: string; lines: string[]; + pageNumber: number; } +/** + * Returns a boolean for whether or not the specified section header exists in the book. + * @param bookContents The book to find the header in. + * @param bookSectionHeader The header to search for. + */ export function bookSectionHeaderExists(bookContents: BookContents, bookSectionHeader: string): boolean { - for (const bookSection of bookContents.bookSections) { - if (bookSection.header === bookSectionHeader) { - return true; - } - } - return false; + return bookContents.bookSections.some(section => section.header === bookSectionHeader); } -function getPageDataFromPageNumber(bookContents: BookContents, page: number): BookPage { +function getBookDataForBookContents(bookContents: BookContents): BookData { const textWidget = filestore.widgetStore.decodeWidget(215) as TextWidget; const output = []; - if (page === 1 && bookContents.showTableOfContents) { + let pageNumber = 1; + + const bookPages: { [key: number]: BookPage } = {}; + if (bookContents.showTableOfContents) { output.push(``); bookContents.bookSections.forEach(section => output.push(section.header)); - return { header: `Chapters`, lines: output }; + bookPages[pageNumber] = { header: `Chapters`, lines: output, pageNumber: pageNumber }; + pageNumber++; } - const outputPages: BookPage[] = []; + const sectionLocations: { [key: string]: number } = {}; + bookContents.bookSections.forEach(section => { const wrappedText = wrapText(section.text, 202, textWidget.fontId); - let pageLineAmount = 14; - - for (let line = 0; line < wrappedText.length; line += pageLineAmount) { - if ((pageLineAmount + line) > (wrappedText.length - line)) { - pageLineAmount = wrappedText.length - line; - } - if (line === 0) { - outputPages.push({ header: section.header, lines: wrappedText.slice(line, line + pageLineAmount) }); - } else { - outputPages.push({ lines: wrappedText.slice(line, line + pageLineAmount) }); + const pageLineAmount = 14; + + let pageContainsHeader = true; + while (wrappedText.length) { + const pageLines = wrappedText.splice(0, pageLineAmount); + bookPages[pageNumber] = { + header: (pageContainsHeader ? section.header : undefined), + lines: pageLines, + pageNumber: pageNumber + }; + if (pageContainsHeader) { + sectionLocations[section.header] = pageNumber; } + pageContainsHeader = false; + pageNumber++; } }); - if(outputPages[page - 2] === undefined) { - return undefined; - } - return outputPages[page - 2]; + logger.info(`Book: ` + bookContents.bookTitle + ` has ` + Object.keys(sectionLocations).length + ` sections.`) + return { sectionLocations: sectionLocations, bookPages: bookPages, bookContents: bookContents }; } -export const pageExists = (book: BookContents, page: number): boolean => { - return book.bookSections[page - 1] !== undefined; +/** + * An enum that represents either the left, or the right page in a book. + */ +export enum PageSide { + LEFT_SIDE = 'LEFT', + RIGHT_SIDE = 'RIGHT' } -function getBookPagesFromBookContents(bookContents: BookContents): { [key: number]: BookPage } { - const bookPages: { [key: number]: BookPage } = {}; - let pageNumber = 1; - let bookPage = getPageDataFromPageNumber(bookContents, pageNumber); - while (bookPage !== undefined) { - bookPages[pageNumber] = bookPage; - pageNumber++; - bookPage = getPageDataFromPageNumber(bookContents, pageNumber); - } - return bookPages; +export const pageExists = (book: BookData, page: number): boolean => { + return (book.bookPages[page] !== undefined); } +export function loadBookData(path: string): BookData[] | null { + const books: BookData[] = []; -function getSectionLocationsFromBookContents(bookContents: BookContents): { [key: string]: number } { - const sectionLocations: { [key: string]: number } = {}; - // const bookPages: { [key: number]: BookPage } = {}; - let pageNumber = 1; - let leftPageData = getPageDataFromPageNumber(bookContents, (2 * pageNumber) - 1); - let rightPageData = getPageDataFromPageNumber(bookContents, (2 * pageNumber)); - - while (leftPageData.lines !== undefined && rightPageData.lines !== undefined) { - - const leftBookPage = (2 * pageNumber) - 1; - const rightBookPage = (2 * pageNumber) - 1; - leftPageData = getPageDataFromPageNumber(bookContents, leftBookPage); - rightPageData = getPageDataFromPageNumber(bookContents, rightBookPage); - - if(leftPageData === undefined) { - return; - } - if(rightPageData === undefined) { - return; - } - if (leftPageData.header) { - sectionLocations[leftPageData.header] = leftBookPage; - } - if (rightPageData.header) { - sectionLocations[rightPageData.header] = rightBookPage; - } - pageNumber += 1; - - - } - return sectionLocations; + fs.readdir(path, function(error, filenames) { + filenames.forEach(function(filename) { + const bookContents = safeLoad(readFileSync(path + filename, 'utf8'), + { schema: JSON_SCHEMA }) as BookContents; + const bookData = getBookDataForBookContents(bookContents); + books.push(bookData); + }); + }); + return books; } -export function loadStrongholdOfSecurityBookData(path: string): BookData | null { - try { - const book = safeLoad(readFileSync(path, 'utf8'), - { schema: JSON_SCHEMA }) as BookContents; - if (!book) { - throw new Error('Unable to read book data!'); - } - - const sectionLocations = getSectionLocationsFromBookContents(book); - const bookPages = getBookPagesFromBookContents(book); - return { bookPages: bookPages, bookContents: book, sectionLocations: sectionLocations }; - } catch (error) { - logger.error('Error parsing book data: ' + error); - } -} diff --git a/src/game-engine/world/skill-util/harvest-skill.ts b/src/game-engine/world/skill-util/harvest-skill.ts index 48c7fd862..63bfc0f54 100644 --- a/src/game-engine/world/skill-util/harvest-skill.ts +++ b/src/game-engine/world/skill-util/harvest-skill.ts @@ -16,7 +16,7 @@ export function canInitiateHarvest(player: Player, target: IHarvestable, skill: if (!target) { switch (skill) { case Skill.MINING: - player.sendMessage('There is current no ore available in this rock.'); + player.sendMessage('There is currently no ore available in this rock.'); break; default: player.sendMessage(colorText('HARVEST SKILL ERROR, PLEASE CONTACT DEVELOPERS', colors.red)); diff --git a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-book.plugin.ts b/src/plugins/books/books.plugin.ts similarity index 79% rename from src/plugins/dungeons/stronghold-of-security/stronghold-of-security-book.plugin.ts rename to src/plugins/books/books.plugin.ts index 51740b123..2304af286 100644 --- a/src/plugins/dungeons/stronghold-of-security/stronghold-of-security-book.plugin.ts +++ b/src/plugins/books/books.plugin.ts @@ -1,10 +1,20 @@ import { ItemInteractionAction, itemInteractionActionHandler } from '@engine/world/action/item-interaction.action'; -import { strongholdOfSecurityBookData, widgets } from '@engine/config'; +import { + getBookFromId, + widgets +} from '@engine/config'; import { TaskExecutor } from '@engine/world/action'; import { widgetInteractionActionHandler } from '@engine/world/action/widget-interaction.action'; import { Player } from '@engine/world/actor/player/player'; -import { BookData, bookSectionHeaderExists, BookSections, pageExists } from '@engine/config/sectioned-book-config'; +import { + BookData, + BookPage, + bookSectionHeaderExists, + BookSections, + pageExists, PageSide +} from '@engine/config/sectioned-book-config'; import { Widget, WidgetClosedEvent } from '@engine/world/actor/player/interface-state'; +import { logger } from '@runejs/core'; /** * Open the book interface and read the specified book. @@ -12,8 +22,17 @@ import { Widget, WidgetClosedEvent } from '@engine/world/actor/player/interface- * @param details Information about the action. */ export const activate: itemInteractionActionHandler = (details) => { + const itemId = details.itemId; + const book = getBookFromId(itemId); + if(!book) { + details.player.sendMessage(`Book data not found for item: ` + itemId + `.`); + return; + } + + details.player.sessionMetadata[`bookIdBeingRead`] = itemId; details.player.playAnimation(1350); - openBook(details.player, strongholdOfSecurityBookData, 1); + + openBook(details.player, book, 1); details.player.metadata['readingBook'] = details.player.interfaceState.closed.subscribe((whatClosed: WidgetClosedEvent) => { if (whatClosed && whatClosed.widget && whatClosed.widget.widgetId === widgets.book) { details.player.stopGraphics(); @@ -28,25 +47,7 @@ export const activate: itemInteractionActionHandler = (details) => { * @param bookSectionHeader The name of the section to find the page of. */ function getPageNumberForBookSection(bookData: BookData, bookSectionHeader: string): number { - let pageNumber = 1; - let leftPageNumber = (2 * pageNumber) - 1; - let rightPageNumber = 2 * pageNumber; - - let leftPageData = bookData.bookPages[leftPageNumber]; - let rightPageData = bookData.bookPages[rightPageNumber]; - while (leftPageData.lines !== undefined && rightPageData.lines !== undefined) { - if (leftPageData.header === bookSectionHeader) { - return pageNumber; - } else if (rightPageData.header === bookSectionHeader) { - return pageNumber; - } - pageNumber += 1; - leftPageNumber = (2 * pageNumber) - 1; - rightPageNumber = (2 * pageNumber); - leftPageData = bookData.bookPages[leftPageNumber]; - rightPageData = bookData.bookPages[rightPageNumber]; - } - return 1; + return bookData.sectionLocations[bookSectionHeader]; } /** @@ -62,7 +63,7 @@ function getPageNumberForBookSection(bookData: BookData, bookSectionHeader: stri */ function toggleVisibilityOfLeftPageClickableWidgets(player: Player, widget: Widget, hidden: boolean, fromLine?: number, toLine?: number) { if (toLine === undefined) { - toLine = 15; + toLine = 14; } if (fromLine === undefined) { @@ -118,34 +119,35 @@ function clearBookInterface(player: Player, widget: Widget) { * @param page The page-set that will be viewed. (This number accounts for two pages at a time. Page 1 and 2 would be 1. * Pages 3 and 4 would be 2, etc.) */ -export function openBook(player: Player, bookData: BookData, page: number): void { +export function openBook(player: Player, bookData: BookData, leftPageNumber: number): void { const widget = player.interfaceState.openWidget(widgets.book, { slot: 'screen', fakeWidget: 3100003, metadata: { - page: page + page: leftPageNumber } }); clearBookInterface(player, widget); + addBookTitleToBookWidget(player, widget, bookData); + toggleVisibilityOfLeftPageClickableWidgets(player, widget, true); - const leftPageNumber = (2 * page) - 1; const leftPage = bookData.bookPages[leftPageNumber]; - if (!leftPage) { + if (leftPage) { + addBookTextToWidget(player, widget, bookData, leftPageNumber, PageSide.LEFT_SIDE); + } else { return; } - addBookTextToWidget(player, widget, bookData, leftPageNumber, PageSide.LEFT_SIDE); - - const rightPageNumber = (2 * page); + const rightPageNumber = leftPageNumber + 1; const rightPage = bookData.bookPages[rightPageNumber]; if (rightPage) { addBookTextToWidget(player, widget, bookData, rightPageNumber, PageSide.RIGHT_SIDE); } - addPageNumberToBookWidget(player, widget); + addPageNumbersToBookWidget(player, widget); } /** @@ -153,17 +155,32 @@ export function openBook(player: Player, bookData: BookData, page: number): void * @param player The player who's widget is being modified. * @param widget The widget to modify with new page numbers. */ -function addPageNumberToBookWidget(player: Player, widget: Widget) { +function addPageNumbersToBookWidget(player: Player, widget: Widget) { player.modifyWidget(widget.widgetId, { childId: widgets.bookChildren.rightPage.pageNumber, - text: `Page ${widget.metadata.page * 2}` + text: `Page ${widget.metadata.page + 1}` }); player.modifyWidget(widget.widgetId, { childId: widgets.bookChildren.leftPage.pageNumber, - text: `Page ${widget.metadata.page * 2 - 1}` + text: `Page ${widget.metadata.page}` }); } +function addBookTitleToBookWidget(player: Player, bookWidget: Widget, bookData: BookData) { + player.modifyWidget(bookWidget.widgetId, { + childId: widgets.bookChildren.title, + text: bookData.bookContents.bookTitle + }); +} + +/** + * Return the appropriate BookPage, given a particular page number in a book. + * @param bookData + * @param page + */ +function getBookPageFromPageNumber(bookData: BookData, page: number): BookPage { + return bookData.bookPages[page]; +} /** * Given a BookData object, and information about where the book data should be applied to, add the appropriate book text @@ -175,7 +192,10 @@ function addPageNumberToBookWidget(player: Player, widget: Widget) { * @param side Whether or not the page is on the left or right side. */ function addBookTextToWidget(player: Player, widget: Widget, book: BookData, pageNumber: number, side: PageSide) { - const page = book.bookPages[pageNumber]; + const page = getBookPageFromPageNumber(book, pageNumber); + if(!page) { + logger.info(`Page number not found.`) + } const clickable = (book.bookContents.showTableOfContents && pageNumber === 1); const totalPageLines = widgets.bookChildren.totalPageLineAmount; @@ -252,25 +272,22 @@ function getLineChildId(pageSide: PageSide, clickable: boolean, lineNumber: numb return pageWidgetData.firstLineId + (lineNumber * pageWidgetData.incrementAmount); } -/** - * An enum that represents either the left, or the right page in a book. - */ -enum PageSide { - LEFT_SIDE = 'LEFT', - RIGHT_SIDE = 'RIGHT' -} - /** * Handles interactions with the book interface itself, such as using the left and right buttons * @param details */ -export const strongholdBookInteract: widgetInteractionActionHandler = (details) => { +export const bookInteract: widgetInteractionActionHandler = (details) => { const playerWidget = details.player.interfaceState.findWidget(27); if (!playerWidget || !playerWidget.metadata.page || playerWidget.fakeWidget !== 3100003) { return; } - const bookData: BookData = strongholdOfSecurityBookData; + + if(!details.player.sessionMetadata[`bookIdBeingRead`]) { + details.player.sendMessage(`Session metadata not found for book interface`); + } + + const bookData: BookData = getBookFromId(details.player.sessionMetadata[`bookIdBeingRead`]); let pageNumber = playerWidget.metadata.page; @@ -279,8 +296,8 @@ export const strongholdBookInteract: widgetInteractionActionHandler = (details) openBook(details.player, bookData, 1); return; case 94: - pageNumber--; - if (pageExists(bookData.bookContents, pageNumber)) { + pageNumber -= 2; + if (pageExists(bookData, pageNumber)) { details.player.playAnimation(3141); openBook(details.player, bookData, pageNumber); } else { @@ -288,8 +305,8 @@ export const strongholdBookInteract: widgetInteractionActionHandler = (details) } return; case 96: - pageNumber++; - if (pageExists(bookData.bookContents, pageNumber)) { + pageNumber += 2; + if (pageExists(bookData, pageNumber)) { details.player.playAnimation(3140); openBook(details.player, bookData, pageNumber); } else { @@ -311,15 +328,22 @@ export const strongholdBookInteract: widgetInteractionActionHandler = (details) if (bookSectionHeaderExists(bookData.bookContents, bookSection.header)) { const sectionName = bookSection.header; - const selectedPage = getPageNumberForBookSection(bookData, sectionName); + let selectedPage = getPageNumberForBookSection(bookData, sectionName); + details.player.sendMessage(`Selected page number: ` + selectedPage) + details.player.sendMessage(`Header: ` + sectionName) + if(selectedPage % 2 === 0) { + selectedPage--; + } + details.player.sendMessage(`Book section header exists.`) + openBook(details.player, bookData, selectedPage); } else { + details.player.sendMessage(`Book section header doesn't exist.`) openBook(details.player, bookData, pageNumber); } } } - const canActivate = (task: TaskExecutor, taskIteration: number): boolean => { return true; } @@ -330,7 +354,7 @@ const onComplete = (task: TaskExecutor): void => { } export default { - pluginId: 'rs:stronghold_of_security_book', + pluginId: 'rs:books', hooks: [ { type: 'item_interaction', @@ -342,7 +366,7 @@ export default { { type: 'widget_interaction', widgetId: 3100003, - handler: strongholdBookInteract + handler: bookInteract } ] }; From 18c675341710c920cbb11ed6917f8ff5eb02947f Mon Sep 17 00:00:00 2001 From: Spencer Carlson Date: Wed, 26 May 2021 14:06:00 -0700 Subject: [PATCH 7/7] Add: More Stronghold of security questions and responses. --- data/config/stronghold-of-security-quiz.json5 | 854 ++++++++++-------- 1 file changed, 467 insertions(+), 387 deletions(-) diff --git a/data/config/stronghold-of-security-quiz.json5 b/data/config/stronghold-of-security-quiz.json5 index 1fc746735..60fb9c520 100644 --- a/data/config/stronghold-of-security-quiz.json5 +++ b/data/config/stronghold-of-security-quiz.json5 @@ -1,390 +1,470 @@ { - prefix: "To pass you must answer me this: ", - questions: [ - { - questionText: "How often should you change your recovery questions?", - options: [ - { - optionText: "Never", - passable: false, - doorResponse: "Correct! This is the ideal, every few months change your questions, but make sure you can remember the answers! Don't use personal details for your recoveries." - }, - { - optionText: "Every day", - passable: false, - doorResponse: "Normally recovery questions will take 14 days to become active, so there's no point in changing them everyday! Don't use personal details for your recoveries." - }, - { - optionText: "Every couple of months", - passable: true, - doorResponse: "Correct! This is the ideal, every few months change your questions, but make sure you can remember the answers! Don't use personal details for your recoveries." - } - ] - }, - { - questionText: "What do I do if a moderator asks me for my account details?", - options: [ - { - optionText: "Tell them whatever they want to know.", - passable: false, - doorResponse: "Wrong! Never give your account details to anyone! This includes things like account creation details, contact details and passwords. Never use personal details for passwords or bank PINs!" - }, - { - optionText: "Politely tell them no.", - passable: true, - doorResponse: "Ok! Don't tell them the details. But reporting the incident to Jagex would help. Use the Report Abuse button. Never use personal details for passwords or bank PINs!" - }, - { - optionText: "Politely tell them no then use the report abuse button.", - passable: true, - doorResponse: "Correct! Report any attempt to gain your account details as it is a very serious breach of RuneScape's rules. Never use personal details for security answers or bank PINs!" - } - ] - }, - { - questionText: "Who can I give my password to?", - options: [ - { - optionText: "My friends.", - passable: false, - doorResponse: "Wrong! Your password should be kept secret from everyone. You should *never* give it out under any circumstances." - }, - { - optionText: "My brother or sister.", - passable: false, - doorResponse: "Wrong! Your password should be kept secret from everyone. You should *never* give it out under any circumstances." - }, - { - optionText: "Nobody.", - passable: true, - doorResponse: "Correct! Your password should be kept secret from everyone. You should *never* give it out under any circumstances." - } - ] - }, - { - questionText: "How do I set a bank PIN?", - options: [ - { - optionText: "Use the account management section on the website.", - passable: false, - doorResponse: "Wrong! Your password can be changed from the account management section, but you must talk to a banker to set a bank PIN. Never use personal details for passwords or bank PINs!" - }, - { - optionText: "Talk to any banker.", - passable: true, - doorResponse: "Correct! Simply talking to a banker will give you the option to set a bank PIN. Never use personal details for passwords or bank PINs!" - } - ] - }, - { - questionText: "Who is it ok to share my account with?", - options: [ - { - optionText: "My friends.", - passable: false, - doorResponse: "Wrong! Your account may only be used by you." - }, - { - optionText: "My relatives.", - passable: false, - doorResponse: "Wrong! Your account may only be used by you." - }, - { - optionText: "Nobody.", - passable: true, - doorResponse: "Correct! Your account may only be used by you." - } - ] - }, - { - questionText: "My friend asks me for my password so that he can do a difficult quest for me. Do I give it to him?", - options: [ - { - optionText: "Yes. He is my best friend and I already spent ages trying this quest.", - passable: false, - doorResponse: "Wrong! Don't give your password to anyone otherwise you can lose everything you have worked so hard for." - }, - { - optionText: "Don't give him my passwoord.", - passable: true, - doorResponse: "Correct! You can make it alone and the success will taste even better. Don't forget you can ask people for advice too!" - }, - { - optionText: "Let him do the quest but in the same room the whole time.", - passable: false, - doorResponse: "Wrong! Never let anyone use your account for any reason. It only takes a few seconds to change your password." - } - ] - }, - { - questionText: "Will Jagex block me from saying my PIN in game?", - options: [ - { - optionText: "Yes.", - passable: false, - doorResponse: "Wrong! Jagex does NOT block your PIN so don't type it! Never use personal details for recoveries or bank PINs!" - }, - { - optionText: "No.", - passable: true, - doorResponse: "Correct! Jagex will not block your PIN so don't type it! Never use personal details for recoveries or bank PINs!" - } - ] - }, - { - questionText: "What do I do if I think I have a keylogger or a virus?", - options: [ - { - optionText: "Virus scan my computer then change my password and recoveries.", - passable: true, - doorResponse: "Correct! Removing the keylogger must be the priority, otherwise anything you type can be given away. Remember to change your password and bank PIN afterwards." - }, - { - optionText: "Change my password then virus scan my computer.", - passable: false, - doorResponse: "Wrong! If you change your password while you still have the keylogger, it will still be insecure. Remove the keylogger first. Never use personal details for passwords or bank PINs!" - }, - { - optionText: "Nothing. It will go away on its own.", - passable: false, - doorResponse: "Wrong! This could mean your account may be accessed by someone else. Remove the keylogger then change your password. Never use personal details for security answers or bank PINs!" - } - ] - }, - { - questionText: "A website says I can become a player moderator by giving them my password, what do I do?", - options: [ - { - optionText: "Nothing.", - passable: false, - doorResponse: "Quite good. But we should try to stop scammers. So please reoport any attempt to gain your account details as it is a very serious breach of RuneScape's rules. Never use personal details for passwords or bank PINs!" - }, - { - optionText: "Give them my password.", - passable: false, - doorResponse: "Wrong! This will almost certainly lead to your account being hijacked. No website can make you a moderator as they are hand picked by Jagex." - }, - { - optionText: "Don't tell them anything and imform Jagex through the game website.", - passable: true, - doorResponse: "Correct! By informing us we can have the site taken down so other people will not have their accounts hijacked by this scam. Remember that moderators are hand picked by Jagex." - } - ] - }, - { - questionText: "What do you do if someone asks you for your password or recoveries to make you a member for free?", - options: [ - { - optionText: "Give them the information they asked for.", - passable: false, - doorResponse: "Wrong! Never give your account details to anyone! This includes things like account creation details, contact details and passwords. Never use personal details for passwords or bank PINs!" - }, - { - optionText: "Don't tell them anything and ignore them.", - passable: true, - doorResponse: "Quite good. But we should try to stop scammers. So please report them using the 'Report Abuse' button." - }, - { - optionText: "Don't tell them anything and click the 'Report Abuse' button.", - passable: true, - doorResponse: "Correct! Press the 'Report Abuse' button and fill in the offending player's name and the correct category." - } - ] - }, - { - questionText: "Where should I enter my RuneScape password?", - options: [ - { - optionText: "On RuneScape and all fansites.", - passable: false, - doorResponse: "Wrong! Always use a unique password purely for your RuneScape account." - }, - { - optionText: "Only on the RuneScape website.", - passable: true, - doorResponse: "Correct! Always make sure you are entering password only on the RuneScape website as other sites may try to steal it." - }, - { - optionText: "On all websites I visit.", - passable: false, - doorResponse: "Wrong! This is very insecure and will may lead to your account being stolen.!" - } - ] - }, - { - questionText: "Where can I find cheats for RuneScape?", - options: [ - { - optionText: "On the RuneScape website.", - passable: false, - doorResponse: "Wrong! There are NO RuneScape cheats coded into the game. Any sites claiming to have cheats are fakes and may lead to your account being stolen if you given them your password." - }, - { - optionText: "By searching the internet.", - passable: false, - doorResponse: "Wrong! There are NO RuneScape cheats coded into the game. Any sites claiming to have cheats are fakes and may lead to your account being stolen if you given them your password." - }, - { - optionText: "Nowhere.", - passable: true, - doorResponse: "Correct! There are NO RuneScape cheats coded into the game. Any sites claiming to have cheats are fakes and may lead to your account being stolen if you given them your password." - } - ] - }, - { - questionText: "What do you do if someone asks you for your password or recoveries to make you a player moderator?", - options: [ - { - optionText: "Don't give them the information and send a 'Abuse report'.", - passable: true, - doorResponse: "Correct! Use the 'Report Abuse' button and fill in the offending player's name and the correct category." - }, - { - optionText: "Don't tell them anything and ignore them.", - passable: true, - doorResponse: "Quite good. But we should try to stop scammers. So please report them using the 'Report Abuse' button." - }, - { - optionText: "Give them the information they asked for.", - passable: false, - doorResponse: "Wrong! Jagex never ask for your account information - especially to become a player moderator. Press the 'Report Abuse' button and fill in the offending player's name and the correct category." - } - ] - }, - { - questionText: "Why do I need to type in recovery questions?", - options: [ - { - optionText: "To help me recover my password if I forget it or it is stolen.", - passable: true, - doorResponse: "Correct! Your recovery questions will help Jagex staff protect and return your account if it is stolen. Never use personal details for recoveries or bank PINs!" - }, - { - optionText: "To let Jagex know more about its players.", - passable: false, - doorResponse: "INCORRECTRESPONSE" - }, - { - optionText: "To see if I can type in random letters on my keyboard.", - passable: false, - doorResponse: "INCORRECTRESPONSE" - } - ] - }, - { - questionText: "What should I do if I think someone knows my recovery answers?", - options: [ - { - optionText: "Tell them never to use them.", - passable: false, - doorResponse: "INCORRECTRESPONSE" - }, - { - optionText: "Use the Account Management section on the RuneScape website.", - passable: false, - doorResponse: "Wrong! If you use the Account Management section to change your recovery questions, it will take 14 days to come into effect, someone may have access to your account in this time." - }, - { - optionText: "Use the 'Recover a Lost Password' section on the RuneScape website.", - passable: false, - doorResponse: "INCORRECTRESPONSE" - } - ] - }, - { - questionText: "Can I leave my account logged in while I'm out of the room?", - options: [ - { - optionText: "If I'm going to be quick.", - passable: false, - doorResponse: "Wrong! You should logout in case you are attacked or receive a random event. Leaving your character logged in can also allow someone to steal your items or entire account!" - }, - { - optionText: "Yes.", - passable: false, - doorResponse: "Wrong! You should logout in case you are attacked or receive a random event. Leaving your character logged in can also allow someone to steal your items or entire account!" - }, - { - optionText: "No.", - passable: true, - doorResponse: "Correct! This is the safest, both in terms of security and keeping your items! Leaving your character logged in can also allow someone to steal your items or entire account!" - } - ] - }, - { - questionText: "Does Jagex really hide your password if you accidentally say it in game?", - options: [ - { - optionText: "Yes - Jagex blocks your password.", - passable: false, - doorResponse: "Wrong! Jagex does NOT block your password so don't type it! Never use personal details for passwords or bank PINs!" - }, - { - optionText: "No - Jagex does not block your password.", - passable: true, - doorResponse: "Correct! We do not block your password. Do not say it in game as someone may steal your account!" - } - ] - }, - { - questionText: "My friend uses this great add-on program he got from a website, should I?", - options: [ - { - optionText: "No, it might steal my password.", - passable: true, - doorResponse: "Correct! The only safe add-on for RuneScape is the Windows client available from our RuneScape website." - }, - { - optionText: "I'll give it a try and see if I like it.", - passable: false, - doorResponse: "Wrong! The program may steal your password and is against the rules to use." - }, - { - optionText: "Sure, he's used it a lot, so can I.", - passable: false, - doorResponse: "Wrong! The program may steal your password and is against the rules to use." - } - ] - }, - { - questionText: "What do you do if someone tells you that you have won the RuneScape Lottery and asks you for your password or recoveries to award your prize?", - options: [ - { - optionText: "Give them the information they asked for.", - passable: false, - doorResponse: "Wrong! This will almost certainly lead to your account being hijacked. [No website can make you a moderator as they are hand picked by Jagex.]" - }, - { - optionText: "Don't tell them anything and ignore them.", - passable: true, - doorResponse: "Quite good. But we should try to stop scammers. So please report them using the 'Report Abuse' button." - }, - { - optionText: "Don't tell them anything and click the 'Report Abuse' button.", - passable: true, - doorResponse: "Correct! Press the 'Report Abuse' button and fill in the offending player's name and the correct category." - } - ] - }, - { - questionText: "What are recovery questions used for?", - options: [ - { - optionText: "Recovering your account if it is stolen.", - passable: false, - doorResponse: "INCORRECTRESPONSE" - }, - { - optionText: "Recovering your account if you forget your password.", - passable: true, - doorResponse: "Correct! If you set recovery questions that you can remember, you can use them to set a new password on your account if you forget the current one. Don't use personal details for your recoveries." - }, - { - optionText: "Recovering your billing details.", - passable: false, - doorResponse: "INCORRECTRESPONSE" + prefix: "To pass you must answer me this: ", + questions: [ + { + questionText: "How often should you change your recovery questions?", + options: [ + { + optionText: "Never", + passable: false, + doorResponse: "Correct! This is the ideal, every few months change your questions, but make sure you can remember the answers! Don't use personal details for your recoveries." + }, + { + optionText: "Every day", + passable: false, + doorResponse: "Normally recovery questions will take 14 days to become active, so there's no point in changing them everyday! Don't use personal details for your recoveries." + }, + { + optionText: "Every couple of months", + passable: true, + doorResponse: "Correct! This is the ideal, every few months change your questions, but make sure you can remember the answers! Don't use personal details for your recoveries." + } + ] + }, + { + questionText: "What do I do if a moderator asks me for my account details?", + options: [ + { + optionText: "Tell them whatever they want to know.", + passable: false, + doorResponse: "Wrong! Never give your account details to anyone! This includes things like account creation details, contact details and passwords. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Politely tell them no.", + passable: true, + doorResponse: "Ok! Don't tell them the details. But reporting the incident to Jagex would help. Use the Report Abuse button. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Politely tell them no then use the report abuse button.", + passable: true, + doorResponse: "Correct! Report any attempt to gain your account details as it is a very serious breach of RuneScape's rules. Never use personal details for security answers or bank PINs!" + } + ] + }, + { + questionText: "Who can I give my password to?", + options: [ + { + optionText: "My friends.", + passable: false, + doorResponse: "Wrong! Your password should be kept secret from everyone. You should *never* give it out under any circumstances." + }, + { + optionText: "My brother or sister.", + passable: false, + doorResponse: "Wrong! Your password should be kept secret from everyone. You should *never* give it out under any circumstances." + }, + { + optionText: "Nobody.", + passable: true, + doorResponse: "Correct! Your password should be kept secret from everyone. You should *never* give it out under any circumstances." + } + ] + }, + { + questionText: "How do I set a bank PIN?", + options: [ + { + optionText: "Use the account management section on the website.", + passable: false, + doorResponse: "Wrong! Your password can be changed from the account management section, but you must talk to a banker to set a bank PIN. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Talk to any banker.", + passable: true, + doorResponse: "Correct! Simply talking to a banker will give you the option to set a bank PIN. Never use personal details for passwords or bank PINs!" + } + ] + }, + { + questionText: "Who is it ok to share my account with?", + options: [ + { + optionText: "My friends.", + passable: false, + doorResponse: "Wrong! Your account may only be used by you." + }, + { + optionText: "My relatives.", + passable: false, + doorResponse: "Wrong! Your account may only be used by you." + }, + { + optionText: "Nobody.", + passable: true, + doorResponse: "Correct! Your account may only be used by you." + } + ] + }, + { + questionText: "My friend asks me for my password so that he can do a difficult quest for me. Do I give it to him?", + options: [ + { + optionText: "Yes. He is my best friend and I already spent ages trying this quest.", + passable: false, + doorResponse: "Wrong! Don't give your password to anyone otherwise you can lose everything you have worked so hard for." + }, + { + optionText: "Don't give him my passwoord.", + passable: true, + doorResponse: "Correct! You can make it alone and the success will taste even better. Don't forget you can ask people for advice too!" + }, + { + optionText: "Let him do the quest but in the same room the whole time.", + passable: false, + doorResponse: "Wrong! Never let anyone use your account for any reason. It only takes a few seconds to change your password." + } + ] + }, + { + questionText: "Will Jagex block me from saying my PIN in game?", + options: [ + { + optionText: "Yes.", + passable: false, + doorResponse: "Wrong! Jagex does NOT block your PIN so don't type it! Never use personal details for recoveries or bank PINs!" + }, + { + optionText: "No.", + passable: true, + doorResponse: "Correct! Jagex will not block your PIN so don't type it! Never use personal details for recoveries or bank PINs!" + } + ] + }, + { + questionText: "What do I do if I think I have a keylogger or a virus?", + options: [ + { + optionText: "Virus scan my computer then change my password and recoveries.", + passable: true, + doorResponse: "Correct! Removing the keylogger must be the priority, otherwise anything you type can be given away. Remember to change your password and bank PIN afterwards." + }, + { + optionText: "Change my password then virus scan my computer.", + passable: false, + doorResponse: "Wrong! If you change your password while you still have the keylogger, it will still be insecure. Remove the keylogger first. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Nothing. It will go away on its own.", + passable: false, + doorResponse: "Wrong! This could mean your account may be accessed by someone else. Remove the keylogger then change your password. Never use personal details for security answers or bank PINs!" + } + ] + }, + { + questionText: "A website says I can become a player moderator by giving them my password, what do I do?", + options: [ + { + optionText: "Nothing.", + passable: false, + doorResponse: "Quite good. But we should try to stop scammers. So please reoport any attempt to gain your account details as it is a very serious breach of RuneScape's rules. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Give them my password.", + passable: false, + doorResponse: "Wrong! This will almost certainly lead to your account being hijacked. No website can make you a moderator as they are hand picked by Jagex." + }, + { + optionText: "Don't tell them anything and imform Jagex through the game website.", + passable: true, + doorResponse: "Correct! By informing us we can have the site taken down so other people will not have their accounts hijacked by this scam. Remember that moderators are hand picked by Jagex." + } + ] + }, + { + questionText: "What do you do if someone asks you for your password or recoveries to make you a member for free?", + options: [ + { + optionText: "Give them the information they asked for.", + passable: false, + doorResponse: "Wrong! Never give your account details to anyone! This includes things like account creation details, contact details and passwords. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Don't tell them anything and ignore them.", + passable: true, + doorResponse: "Quite good. But we should try to stop scammers. So please report them using the 'Report Abuse' button." + }, + { + optionText: "Don't tell them anything and click the 'Report Abuse' button.", + passable: true, + doorResponse: "Correct! Press the 'Report Abuse' button and fill in the offending player's name and the correct category." + } + ] + }, + { + questionText: "Where should I enter my RuneScape password?", + options: [ + { + optionText: "On RuneScape and all fansites.", + passable: false, + doorResponse: "Wrong! Always use a unique password purely for your RuneScape account." + }, + { + optionText: "Only on the RuneScape website.", + passable: true, + doorResponse: "Correct! Always make sure you are entering password only on the RuneScape website as other sites may try to steal it." + }, + { + optionText: "On all websites I visit.", + passable: false, + doorResponse: "Wrong! This is very insecure and will may lead to your account being stolen.!" + } + ] + }, + { + questionText: "Where can I find cheats for RuneScape?", + options: [ + { + optionText: "On the RuneScape website.", + passable: false, + doorResponse: "Wrong! There are NO RuneScape cheats coded into the game. Any sites claiming to have cheats are fakes and may lead to your account being stolen if you given them your password." + }, + { + optionText: "By searching the internet.", + passable: false, + doorResponse: "Wrong! There are NO RuneScape cheats coded into the game. Any sites claiming to have cheats are fakes and may lead to your account being stolen if you given them your password." + }, + { + optionText: "Nowhere.", + passable: true, + doorResponse: "Correct! There are NO RuneScape cheats coded into the game. Any sites claiming to have cheats are fakes and may lead to your account being stolen if you given them your password." + } + ] + }, + { + questionText: "What do you do if someone asks you for your password or recoveries to make you a player moderator?", + options: [ + { + optionText: "Don't give them the information and send a 'Abuse report'.", + passable: true, + doorResponse: "Correct! Use the 'Report Abuse' button and fill in the offending player's name and the correct category." + }, + { + optionText: "Don't tell them anything and ignore them.", + passable: true, + doorResponse: "Quite good. But we should try to stop scammers. So please report them using the 'Report Abuse' button." + }, + { + optionText: "Give them the information they asked for.", + passable: false, + doorResponse: "Wrong! Jagex never ask for your account information - especially to become a player moderator. Press the 'Report Abuse' button and fill in the offending player's name and the correct category." + } + ] + }, + { + questionText: "Why do I need to type in recovery questions?", + options: [ + { + optionText: "To help me recover my password if I forget it or it is stolen.", + passable: true, + doorResponse: "Correct! Your recovery questions will help Jagex staff protect and return your account if it is stolen. Never use personal details for recoveries or bank PINs!" + }, + { + optionText: "To let Jagex know more about its players.", + passable: false, + doorResponse: "INCORRECTRESPONSE" + }, + { + optionText: "To see if I can type in random letters on my keyboard.", + passable: false, + doorResponse: "Incorrect! You should always remember this info so your account is as safe as possible." + } + ] + }, + { + questionText: "What should I do if I think someone knows my recovery answers?", + options: [ + { + optionText: "Tell them never to use them.", + passable: false, + doorResponse: "Wrong! Never give your account details to anyone! This includes things like account creation details, contact details and passwords. Never use personal details for passwords or bank PINs" + }, + { + optionText: "Use the Account Management section on the RuneScape website.", + passable: false, + doorResponse: "Wrong! If you use the Account Management section to change your recovery questions, it will take 14 days to come into effect, someone may have access to your account in this time." + }, + { + optionText: "Use the 'Recover a Lost Password' section on the RuneScape website.", + passable: true, + doorResponse: "Correct! If you provide all the correct information this will reset your questions within 24 hours and make your account secure again." + } + ] + }, + { + questionText: "Recovery answers should be...", + options: [ + { + optionText: "Memorable.", + passable: true, + doorResponse: "Correct! A good recovery answer that not many people will know, you won't forget, will stay the same and that you won't accidentally give away. Remember: don't use personal details for your recoveries." + }, + { + optionText: "Easy to guess.", + passable: false, + doorResponse: "This is a bad idea as anyone who knows you could guess them. Remember: Don't use personal details for your recoveries." + }, + { + optionText: "Random gibberish.", + passable: false, + doorResponse: "Wrong! A good recovery answer that not many people will know, you won't forget, will stay the same and that you won't accidentally give away. Remember: don't use personal details for your recoveries." + } + ] + }, + { + questionText: "What is an example of a good bank PIN?", + options: [ + { + optionText: "Your real life bank PIN.", + passable: false, + doorResponse: "This is a bad idea as if someone happen to find out your bank PIN on RuneScape, they then have your real life bank PIN! Never use personal details for security answers or bank PINs!" + }, + { + optionText: "Your birthday.", + passable: false, + doorResponse: "Not a good idea because you know how many presents you get for your birthday. So you can imagine how many people know this date. Never use personal details for recoveries or bank PINs!." + }, + { + optionText: "The birthday of a famous person or event.", + passable: true, + doorResponse: "Well done! Unless you tell someone, they are unlikely to guess who or what you have chosen, and you can always look it up. Never use personal details for recoveries or bank PINs!" + } + ] + }, + { + questionText: "How will Jagex contact me if I have been chosen to be a moderator?", + options: [ + { + optionText: "Email.", + passable: false, + doorResponse: "Incorrect. Jagex will never email you asking you to become a Moderator. We will contact you through your Message Inbox available on our website." + }, + { + optionText: "Website popup.", + passable: false, + doorResponse: "Incorrect, we will only contact our players via the game Inbox which you can access from our RuneScape website." + }, + { + optionText: "Game inbox on the RuneScape website.", + passable: true, + doorResponse: "Correct! We only contact our players via the game Inbox which you can access from our RuneScape website." + } + ] + }, + { + questionText: "What should you do if your real-life friend asks for your password so he can check your stats?", + options: [ + { + optionText: "Give them your password since they're a friend in real life.", + passable: false, + doorResponse: "Incorrect! Don't trust anybody with your account login details!" + }, + { + optionText: "Don't give out your password to anyone. Not even close friends.", + passable: true, + doorResponse: "Correct! Doing so could result in losing your items and gold and puts your account at risk." + }, + { + optionText: "Log in for your friend and let them play.", + passable: false, + doorResponse: "Incorrect! Never allow anybody else to access your account." + } + ] + }, + { + questionText: "Can I leave my account logged in while I'm out of the room?", + options: [ + { + optionText: "If I'm going to be quick.", + passable: false, + doorResponse: "Wrong! You should logout in case you are attacked or receive a random event. Leaving your character logged in can also allow someone to steal your items or entire account!" + }, + { + optionText: "Yes.", + passable: false, + doorResponse: "Wrong! You should logout in case you are attacked or receive a random event. Leaving your character logged in can also allow someone to steal your items or entire account!" + }, + { + optionText: "No.", + passable: true, + doorResponse: "Correct! This is the safest, both in terms of security and keeping your items! Leaving your character logged in can also allow someone to steal your items or entire account!" + } + ] + }, + { + questionText: "Does Jagex really hide your password if you accidentally say it in game?", + options: [ + { + optionText: "Yes - Jagex blocks your password.", + passable: false, + doorResponse: "Wrong! Jagex does NOT block your password so don't type it! Never use personal details for passwords or bank PINs!" + }, + { + optionText: "No - Jagex does not block your password.", + passable: true, + doorResponse: "Correct! We do not block your password. Do not say it in game as someone may steal your account!" + } + ] + }, + { + questionText: "My friend uses this great add-on program he got from a website, should I?", + options: [ + { + optionText: "No, it might steal my password.", + passable: true, + doorResponse: "Correct! The only safe add-on for RuneScape is the Windows client available from our RuneScape website." + }, + { + optionText: "I'll give it a try and see if I like it.", + passable: false, + doorResponse: "Wrong! The program may steal your password and is against the rules to use." + }, + { + optionText: "Sure, he's used it a lot, so can I.", + passable: false, + doorResponse: "Wrong! The program may steal your password and is against the rules to use." + } + ] + }, + { + questionText: "What do you do if someone tells you that you have won the RuneScape Lottery and asks you for your password or recoveries to award your prize?", + options: [ + { + optionText: "Give them the information they asked for.", + passable: false, + doorResponse: "Wrong! Never give your account details to anyone! This includes things like account creation details, contact details and passwords. Never use personal details for passwords or bank PINs!" + }, + { + optionText: "Don't tell them anything and ignore them.", + passable: true, + doorResponse: "Quite good. But we should try to stop scammers. So please report them using the 'Report Abuse' button." + }, + { + optionText: "Don't tell them anything and click the 'Report Abuse' button.", + passable: true, + doorResponse: "Correct! Press the 'Report Abuse' button and fill in the offending player's name and the correct category." + } + ] + }, + { + questionText: "What are recovery questions used for?", + options: [ + { + optionText: "Recovering your account if it is stolen.", + passable: true, + doorResponse: "Correct! If you set recovery questions that you can remember, you can use them to set a new password on your account if you forget the current one. Don't use personal details for your recoveries." + }, + { + optionText: "Recovering your account if you forget your password.", + passable: true, + doorResponse: "Correct! If you set recovery questions that you can remember, you can use them to set a new password on your account if you forget the current one. Don't use personal details for your recoveries." + }, + { + optionText: "Recovering your billing details.", + passable: false, + doorResponse: "INCORRECTRESPONSE" + } + ] } - ] - } - ] + ] }