diff --git a/.data/gamevals/dbrow.rscm b/.data/gamevals/dbrow.rscm index 18a96f764..dbb0b6093 100644 --- a/.data/gamevals/dbrow.rscm +++ b/.data/gamevals/dbrow.rscm @@ -38,4 +38,472 @@ mining_saltrock_basalt=55511 mining_ancientessence=55512 mining_infernalshale=55513 shop_currency_standard_gp=64596 -shop_currency_stardust=64595 \ No newline at end of file +shop_currency_stardust=64595 +poh_crude_wooden_chair=64130 +poh_wooden_chair=64129 +poh_rocking_chair=64128 +poh_oak_chair=64127 +poh_oak_armchair=64126 +poh_teak_armchair=64125 +poh_mahogany_armchair=64124 +poh_wooden_bookcase=64123 +poh_oak_bookcase=64122 +poh_mahogany_bookcase=64121 +poh_clay_fireplace=64120 +poh_stone_fireplace=64119 +poh_marble_fireplace=64118 +poh_torn_curtains=64117 +poh_curtains=64116 +poh_opulent_curtains=64115 +poh_brown_rug=64114 +poh_rug=64113 +poh_opulent_rug=64112 +poh_firepit=64111 +poh_firepit_with_hook=64110 +poh_firepit_with_pot=64109 +poh_small_oven=64108 +poh_large_oven=64107 +poh_steel_range=64106 +poh_fancy_range=64105 +poh_wooden_shelves_1=64104 +poh_wooden_shelves_2=64103 +poh_wooden_shelves_3=64102 +poh_oak_shelves_1=64101 +poh_oak_shelves_2=64100 +poh_teak_shelves_1=64099 +poh_teak_shelves_2=64098 +poh_pump_and_drain=64097 +poh_pump_and_tub=64096 +poh_kitchen_sink=64095 +poh_gold_sink=64094 +poh_wooden_larder=64093 +poh_oak_larder=64092 +poh_teak_larder=64091 +poh_beer_barrel=64090 +poh_cider_barrel=64089 +poh_asgarnian_ale_barrel=64088 +poh_greenmans_ale_barrel=64087 +poh_dragon_bitter_barrel=64086 +poh_chefs_delight_barrel=64085 +poh_cat_blanket=64084 +poh_cat_basket=64083 +poh_cushioned_basket=64082 +poh_kitchen_table=64081 +poh_oak_kitchen_table=64080 +poh_teak_kitchen_table=64079 +poh_spice_rack=64078 +poh_wood_dining_table=64077 +poh_oak_dining_table=64076 +poh_carved_oak_table=64075 +poh_teak_table=64074 +poh_carved_teak_table=64073 +poh_mahogany_table=64072 +poh_opulent_table=64071 +poh_wooden_bench=64070 +poh_wooden_bench_2=64069 +poh_oak_bench=64068 +poh_oak_bench_2=64067 +poh_carved_oak_bench=64066 +poh_carved_oak_bench_2=64065 +poh_teak_dining_bench=64064 +poh_teak_dining_bench_2=64063 +poh_carved_teak_bench=64062 +poh_carved_teak_bench_2=64061 +poh_mahogany_bench=64060 +poh_mahogany_bench_2=64059 +poh_gilded_bench=64058 +poh_gilded_bench_2=64057 +poh_rope_bellpull=64056 +poh_bellpull=64055 +poh_posh_bellpull=64054 +poh_greenman_carving=64053 +poh_oak_wall_decoration=64052 +poh_teak_wall_decoration=64051 +poh_gilded_decoration=64050 +poh_wooden_bed=64049 +poh_oak_bed=64048 +poh_large_oak_bed=64047 +poh_teak_bed=64046 +poh_large_teak_bed=64045 +poh_four_poster=64044 +poh_gilded_four_poster=64043 +poh_shoe_box=64042 +poh_oak_drawers=64041 +poh_oak_wardrobe=64040 +poh_teak_drawers=64039 +poh_teak_wardrobe=64038 +poh_mahogany_wardrobe=64037 +poh_gilded_wardrobe=64036 +poh_shaving_stand=64035 +poh_oak_shaving_stand=64034 +poh_oak_dresser=64033 +poh_teak_dresser=64032 +poh_fancy_teak_dresser=64031 +poh_mahogany_dresser=64030 +poh_gilded_dresser=64029 +poh_oak_clock=64028 +poh_teak_clock=64027 +poh_servants_moneybag=64026 +poh_gilded_clock=64025 +poh_wooden_workbench=64024 +poh_oak_workbench=64023 +poh_steel_framed_workbench=64022 +poh_bench_with_vice=64021 +poh_bench_with_lathe=64020 +poh_workshop_tool_store_1=64019 +poh_workshop_tool_store_2=64018 +poh_workshop_tool_store_3=64017 +poh_workshop_tool_store_4=64016 +poh_workshop_tool_store_5=64015 +poh_workshop_crafting_table_1=64014 +poh_workshop_crafting_table_2=64013 +poh_workshop_crafting_table_3=64012 +poh_workshop_crafting_table_4=64011 +poh_repair_bench=64010 +poh_whetstone=64009 +poh_armour_stand=64008 +poh_pluming_stand=64007 +poh_shield_easel=64006 +poh_banner_easel=64005 +poh_oak_lectern=64004 +poh_eagle_lectern=64003 +poh_demon_lectern=64002 +poh_teak_eagle_lectern=64001 +poh_teak_demon_lectern=64000 +poh_mahogany_eagle_lectern=63999 +poh_mahogany_demon_lectern=63998 +poh_marble_lectern=63997 +poh_globe=63996 +poh_ornamental_globe=63995 +poh_lunar_globe=63994 +poh_celestial_globe=63993 +poh_armillary_sphere=63992 +poh_small_orrery=63991 +poh_large_orrery=63990 +poh_crystal_ball=63989 +poh_elemental_sphere=63988 +poh_crystal_of_power=63987 +poh_oak_telescope=63986 +poh_teak_telescope=63985 +poh_mahogany_telescope=63984 +poh_alchemical_chart=63983 +poh_astronomical_chart=63982 +poh_infernal_chart=63981 +poh_oak_altar=63980 +poh_teak_altar=63979 +poh_cloth_altar=63978 +poh_mahogany_altar=63977 +poh_limestone_altar=63976 +poh_marble_altar=63975 +poh_gilded_altar=63974 +poh_steel_torches=63973 +poh_wooden_torches=63972 +poh_steel_candlesticks=63971 +poh_gold_candlesticks=63970 +poh_oak_incense_burners=63969 +poh_mahogany_incense_burners=63968 +poh_marble_incense_burners=63967 +poh_saradomin_symbol=63966 +poh_zamorak_symbol=63965 +poh_guthix_symbol=63964 +poh_saradomin_icon=63963 +poh_zamorak_icon=63962 +poh_guthix_icon=63961 +poh_icon_of_bob=63960 +poh_chapel_windchimes=63959 +poh_chapel_bells=63958 +poh_chapel_organ=63957 +poh_small_statue=63956 +poh_medium_statue=63955 +poh_large_statue=63954 +poh_shuttered_window=63953 +poh_decorative_window=63952 +poh_stained_glass=63951 +poh_oak_throne=63950 +poh_teak_throne=63949 +poh_mahogany_throne=63948 +poh_gilded_throne=63947 +poh_skeleton_throne=63946 +poh_crystal_throne=63945 +poh_demonic_throne=63944 +poh_oak_lever=63943 +poh_teak_lever=63942 +poh_mahogany_lever=63941 +poh_oak_trapdoor=63940 +poh_teak_trapdoor=63939 +poh_mahogany_trapdoor=63938 +poh_floor_decoration=63937 +poh_throne_steel_cage=63936 +poh_trapdoor_floor=63935 +poh_lesser_magic_cage=63934 +poh_greater_magic_cage=63933 +poh_throne_carved_teak_bench=63932 +poh_throne_carved_teak_bench_2=63931 +poh_throne_mahogany_bench=63930 +poh_throne_mahogany_bench_2=63929 +poh_throne_gilded_bench=63928 +poh_throne_gilded_bench_2=63927 +poh_round_shield=63926 +poh_square_shield=63925 +poh_kite_shield=63924 +poh_lesser_magical_balance=63923 +poh_medium_magical_balance=63922 +poh_greater_magical_balance=63921 +poh_jester=63920 +poh_treasure_hunt=63919 +poh_hangman=63918 +poh_oak_prize_chest=63917 +poh_teak_prize_chest=63916 +poh_mahogany_prize_chest=63915 +poh_hoop_and_stick=63914 +poh_dartboard=63913 +poh_archery_target=63912 +poh_clay_attack_stone=63911 +poh_limestone_attack_stone=63910 +poh_marble_attack_stone=63909 +poh_boxing_ring=63908 +poh_fencing_ring=63907 +poh_combat_ring=63906 +poh_ranging_pedestals=63905 +poh_boxing_glove_rack=63904 +poh_weapons_rack=63903 +poh_extra_weapons_rack=63902 +poh_combat_dummy_row=63901 +poh_undead_combat_dummy=63900 +poh_ornate_undead_combat_dummy=63899 +poh_teak_portal=63898 +poh_mahogany_portal=63897 +poh_marble_portal=63896 +poh_teleport_focus=63895 +poh_greater_teleport_focus=63894 +poh_scrying_pool=63893 +poh_oak_armour_case=63892 +poh_teak_armour_case=63891 +poh_mahogany_armour_case=63890 +poh_oak_cape_rack=63889 +poh_teak_cape_rack=63888 +poh_mahogany_cape_rack=63887 +poh_gilded_cape_rack=63886 +poh_marble_cape_rack=63885 +poh_magical_cape_rack=63884 +poh_oak_fancy_dress_box=63883 +poh_teak_fancy_dress_box=63882 +poh_mahogany_fancy_dress_box=63881 +poh_oak_magic_wardrobe=63880 +poh_carved_oak_magic_wardrobe=63879 +poh_teak_magic_wardrobe=63878 +poh_carved_teak_magic_wardrobe=63877 +poh_mahogany_magic_wardrobe=63876 +poh_gilded_magic_wardrobe=63875 +poh_marble_magic_wardrobe=63874 +poh_oak_toy_box=63873 +poh_teak_toy_box=63872 +poh_mahogany_toy_box=63871 +poh_oak_treasure_chest=63870 +poh_teak_treasure_chest=63869 +poh_mahogany_treasure_chest=63868 +poh_simple_arena=63867 +poh_advanced_arena=63866 +poh_glorious_arena=63865 +poh_grassland_habitat=63864 +poh_forest_habitat=63863 +poh_desert_habitat=63862 +poh_polar_habitat=63861 +poh_volcanic_habitat=63860 +poh_oak_feeder=63859 +poh_teak_feeder=63858 +poh_mahogany_feeder=63857 +poh_oak_pet_house=63856 +poh_teak_pet_house=63855 +poh_mahogany_pet_house=63854 +poh_consecrated_pet_house=63853 +poh_desecrated_pet_house=63852 +poh_nature_pet_house=63851 +poh_pet_list=63850 +poh_oak_scratching_post=63849 +poh_teak_scratching_post=63848 +poh_mahogany_scratching_post=63847 +poh_dungeon_skeleton_guard=63846 +poh_dungeon_guard_dog=63845 +poh_hobgoblin_guard=63844 +poh_baby_red_dragon=63843 +poh_huge_spider=63842 +poh_troll_guard=63841 +poh_hellhound_guard=63840 +poh_dungeon_spike_trap=63839 +poh_dungeon_man_trap=63838 +poh_tangle_vine=63837 +poh_dungeon_marble_trap=63836 +poh_dungeon_teleport_trap=63835 +poh_oak_door=63834 +poh_oak_door_2=63833 +poh_steel_plated_door=63832 +poh_steel_plated_door_2=63831 +poh_marble_door=63830 +poh_marble_door_2=63829 +poh_dungeon_candles=63828 +poh_dungeon_torches=63827 +poh_dungeon_skull_torches=63826 +poh_decorative_blood=63825 +poh_decorative_pipe=63824 +poh_hanging_skeleton=63823 +poh_oubliette_floor_spikes=63822 +poh_tentacle_pool=63821 +poh_flame_pit=63820 +poh_rocnar=63819 +poh_oak_cage=63818 +poh_oak_and_steel_cage=63817 +poh_oubliette_steel_cage=63816 +poh_spiked_cage=63815 +poh_bone_cage=63814 +poh_oak_ladder=63813 +poh_teak_ladder=63812 +poh_mahogany_ladder=63811 +poh_wooden_crate=63810 +poh_oak_chest=63809 +poh_teak_chest=63808 +poh_mahogany_chest=63807 +poh_magic_chest=63806 +poh_treasure_demon=63805 +poh_treasure_kalphite_soldier=63804 +poh_treasure_tok_xil=63803 +poh_treasure_dagannoth=63802 +poh_treasure_steel_dragon=63801 +poh_treasure_rune_dragon=63800 +poh_hall_rug=63799 +poh_hall_opulent_rug=63798 +poh_oak_staircase=63797 +poh_teak_staircase=63796 +poh_limestone_spiral_staircase=63795 +poh_marble_staircase=63794 +poh_marble_spiral=63793 +poh_teak_head_display=63792 +poh_mahogany_head_display=63791 +poh_gilded_head_display=63790 +poh_oak_fish_display=63789 +poh_teak_fish_display=63788 +poh_mahogany_fish_display=63787 +poh_mithril_armour=63786 +poh_adamantite_armour=63785 +poh_runite_armour=63784 +poh_red_castlewars_armour=63783 +poh_white_castlewars_armour=63782 +poh_gold_castlewars_armour=63781 +poh_rune_case_1=63780 +poh_rune_case_2=63779 +poh_rune_case_3=63778 +poh_king_arthur_portrait=63777 +poh_elena_portrait=63776 +poh_giant_dwarf_portrait=63775 +poh_miscellanians_portrait=63774 +poh_landscape_lumbridge=63773 +poh_landscape_desert=63772 +poh_landscape_morytania=63771 +poh_landscape_karamja=63770 +poh_landscape_isafdar=63769 +poh_mounted_anti_dragon_shield=63768 +poh_mounted_amulet_of_glory=63767 +poh_mounted_cape_of_legends=63766 +poh_mounted_mythical_cape=63765 +poh_mounted_silverlight=63764 +poh_mounted_excalibur=63763 +poh_mounted_darklight=63762 +poh_small_map=63761 +poh_medium_map=63760 +poh_large_map=63759 +poh_exit_portal_build=63758 +poh_garden_greenman_statue=63757 +poh_decorative_rock=63756 +poh_pond=63755 +poh_imp_statue=63754 +poh_dungeon_entrance=63753 +poh_tip_jar=63752 +poh_garden_dead_tree=63751 +poh_garden_nice_tree=63750 +poh_garden_oak_tree=63749 +poh_garden_willow_tree=63748 +poh_garden_maple_tree=63747 +poh_garden_yew_tree=63746 +poh_garden_magic_tree=63745 +poh_garden_fern=63744 +poh_garden_bush=63743 +poh_garden_tall_plant=63742 +poh_garden_short_plant=63741 +poh_garden_large_leaf_bush=63740 +poh_garden_huge_plant=63739 +poh_garden_plant=63738 +poh_garden_small_fern=63737 +poh_garden_fern_small=63736 +poh_garden_dock_leaf=63735 +poh_garden_thistle=63734 +poh_garden_reeds=63733 +poh_gazebo=63732 +poh_small_fountain=63731 +poh_large_fountain=63730 +poh_posh_fountain=63729 +poh_boundary_stones=63728 +poh_wooden_fence=63727 +poh_stone_wall=63726 +poh_iron_railings=63725 +poh_picket_fence=63724 +poh_garden_fence=63723 +poh_marble_wall=63722 +poh_thorny_hedge=63721 +poh_nice_hedge=63720 +poh_small_box_hedge=63719 +poh_topiary_hedge=63718 +poh_fancy_hedge=63717 +poh_tall_fancy_hedge=63716 +poh_tall_box_hedge=63715 +poh_sunflower=63714 +poh_marigolds=63713 +poh_roses=63712 +poh_rosemary=63711 +poh_daffodils=63710 +poh_bluebells=63709 +poh_restoration_pool=63708 +poh_revitalisation_pool=63707 +poh_rejuvenation_pool=63706 +poh_fancy_rejuvenation_pool=63705 +poh_ornate_rejuvenation_pool=63704 +poh_spirit_tree_teleport=63703 +poh_obelisk=63702 +poh_fairy_ring_teleport=63701 +poh_spiritual_fairy_tree=63700 +poh_zen_theme=63699 +poh_otherworldly_theme=63698 +poh_volcanic_theme=63697 +poh_topiary_bush=63696 +poh_redwood_fence=63695 +poh_superior_marble_wall=63694 +poh_obsidian_fence=63693 +poh_teak_garden_bench=63692 +poh_teak_garden_bench_2=63691 +poh_gnome_bench=63690 +poh_gnome_bench_2=63689 +poh_marble_bench=63688 +poh_marble_bench_2=63687 +poh_obsidian_bench=63686 +poh_obsidian_bench_2=63685 +poh_ancient_altar=63684 +poh_lunar_altar=63683 +poh_dark_altar=63682 +poh_occult_altar=63681 +poh_occult_altar_2=63680 +poh_occult_altar_3=63679 +poh_mahogany_adventure_log=63678 +poh_gilded_adventure_log=63677 +poh_marble_adventure_log=63676 +poh_boss_lair_display=63675 +poh_mounted_emblem=63674 +poh_mounted_coins=63673 +poh_cape_hanger=63672 +poh_basic_jewellery_box=63671 +poh_fancy_jewellery_box=63670 +poh_ornate_jewellery_box=63669 +poh_quest_list=63668 +poh_marble_portal_nexus=63667 +poh_gilded_portal_nexus=63666 +poh_crystalline_portal_nexus=63665 +poh_mounted_xerics_talisman=63664 +poh_mounted_digsite_pendant=63663 diff --git a/.data/gamevals/dbtable.rscm b/.data/gamevals/dbtable.rscm index 1926e63a2..7044c1e9b 100644 --- a/.data/gamevals/dbtable.rscm +++ b/.data/gamevals/dbtable.rscm @@ -2,4 +2,5 @@ music_modern=6532 music_classic=6353 gameframe=6534 mining_rocks=55520 -shop_currency=65511 \ No newline at end of file +shop_currency=65511 +poh_furniture=65506 diff --git a/.data/gamevals/droptrigger.rscm b/.data/gamevals/droptrigger.rscm new file mode 100644 index 000000000..156ab9f88 --- /dev/null +++ b/.data/gamevals/droptrigger.rscm @@ -0,0 +1 @@ +poh_building_mode=0 diff --git a/.data/raw-cache/server/inv.toml b/.data/raw-cache/server/inv.toml index aa7b2c19f..763a4eee6 100644 --- a/.data/raw-cache/server/inv.toml +++ b/.data/raw-cache/server/inv.toml @@ -50,3 +50,83 @@ scope = "Perm" stack = "Always" protect = false +[[inventory]] +isServerOnly = true +id = "inv.poh_costume_room_armour_inv" +inherit = "inv.poh_costume_room_armour_inv" +size = 100 +scope = "Perm" +stack = "Always" +protect = false + +[[inventory]] +isServerOnly = true +id = "inv.poh_costume_room_capes_inv" +inherit = "inv.poh_costume_room_capes_inv" +size = 100 +scope = "Perm" +stack = "Always" +protect = false + +[[inventory]] +isServerOnly = true +id = "inv.poh_costume_room_magic_wardrobe_inv" +inherit = "inv.poh_costume_room_magic_wardrobe_inv" +size = 100 +scope = "Perm" +stack = "Always" +protect = false + +[[inventory]] +isServerOnly = true +id = "inv.poh_costume_room_holiday_items_inv" +inherit = "inv.poh_costume_room_holiday_items_inv" +size = 100 +scope = "Perm" +stack = "Always" +protect = false + +[[inventory]] +isServerOnly = true +id = "inv.poh_costume_room_ame_inv" +inherit = "inv.poh_costume_room_ame_inv" +size = 100 +scope = "Perm" +stack = "Always" +protect = false + +[[inventory]] +isServerOnly = true +id = "inv.poh_costume_room_treasure_trail_0_inv" +inherit = "inv.poh_costume_room_treasure_trail_0_inv" +size = 100 +scope = "Perm" +stack = "Always" +protect = false + +[[inventory]] +isServerOnly = true +id = "inv.poh_costume_room_treasure_trail_1_inv" +inherit = "inv.poh_costume_room_treasure_trail_1_inv" +size = 100 +scope = "Perm" +stack = "Always" +protect = false + +[[inventory]] +isServerOnly = true +id = "inv.poh_costume_room_treasure_trail_2_inv" +inherit = "inv.poh_costume_room_treasure_trail_2_inv" +size = 100 +scope = "Perm" +stack = "Always" +protect = false + +[[inventory]] +isServerOnly = true +id = "inv.poh_costume_room_treasure_trail_3_inv" +inherit = "inv.poh_costume_room_treasure_trail_3_inv" +size = 100 +scope = "Perm" +stack = "Always" +protect = false diff --git a/api/game-process/src/integration/kotlin/org/rsmod/api/game/process/player/PlayerZoneUpdateProcessorTest.kt b/api/game-process/src/integration/kotlin/org/rsmod/api/game/process/player/PlayerZoneUpdateProcessorTest.kt index f6d66e108..b5bcb55ea 100644 --- a/api/game-process/src/integration/kotlin/org/rsmod/api/game/process/player/PlayerZoneUpdateProcessorTest.kt +++ b/api/game-process/src/integration/kotlin/org/rsmod/api/game/process/player/PlayerZoneUpdateProcessorTest.kt @@ -79,8 +79,14 @@ class PlayerZoneUpdateProcessorTest { } val startZone = ZoneKey.from(startCoords) + // Zone updates cover every level of the visible zone columns, not just the player's. val expectedZoneKeys = - zoneRange.flatMap { x -> zoneRange.map { z -> startZone.translate(x, z).packed } } + zoneRange.flatMap { x -> + zoneRange.flatMap { z -> + val zone = startZone.translate(x, z) + (0 until 4).map { level -> ZoneKey(zone.x, zone.z, level).packed } + } + } // Set a static map loc in loc registry; this loc should not be sent as a zone // update, or so we will verify. @@ -119,7 +125,7 @@ class PlayerZoneUpdateProcessorTest { process() // `processNewVisibleZones` - assertEquals(49, captured.countOf()) + assertEquals(49 * 4, captured.countOf()) // `refreshVisibleZoneKeys` assertEquals(expectedZoneKeys.toSet(), visibleZoneKeys.toSet()) diff --git a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerZoneUpdateProcessor.kt b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerZoneUpdateProcessor.kt index 7663c59a2..2c51bfc53 100644 --- a/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerZoneUpdateProcessor.kt +++ b/api/game-process/src/main/kotlin/org/rsmod/api/game/process/player/PlayerZoneUpdateProcessor.kt @@ -207,12 +207,22 @@ constructor( else -> backing } + /** + * Every zone within view range of this zone's column, across **all four levels**. Zone + * updates are level-addressed (`UpdateZoneFullFollows`/`UpdateZonePartialEnclosed` carry a + * level), and the client renders every level of the scene: a loc changed on another level - + * e.g. a house's dungeon staircase replacing its baked hotspot below the player - must reach + * observers on other levels or their client keeps showing the stale baked state until they + * happen to visit that level. + */ private fun ZoneKey.computeVisibleNeighbouringZones(): IntList { val zones = IntArrayList(ZONE_VIEW_TOTAL_COUNT) for (x in -ZONE_VIEW_RADIUS..ZONE_VIEW_RADIUS) { for (z in -ZONE_VIEW_RADIUS..ZONE_VIEW_RADIUS) { val zone = translate(x, z) - zones.add(zone.packed) + for (level in 0 until LEVEL_COUNT) { + zones.add(ZoneKey(zone.x, zone.z, level).packed) + } } } return zones @@ -234,8 +244,9 @@ constructor( public companion object { public const val ZONE_VIEW_RADIUS: Int = 3 + public const val LEVEL_COUNT: Int = 4 public const val ZONE_VIEW_TOTAL_COUNT: Int = - (2 * ZONE_VIEW_RADIUS + 1) * (2 * ZONE_VIEW_RADIUS + 1) + (2 * ZONE_VIEW_RADIUS + 1) * (2 * ZONE_VIEW_RADIUS + 1) * LEVEL_COUNT public val BUILD_AREA_BOUNDS: IntRange = 0 until BuildAreaUtils.SIZE } diff --git a/api/poh/build.gradle.kts b/api/poh/build.gradle.kts new file mode 100644 index 000000000..dbc3aeb5e --- /dev/null +++ b/api/poh/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(libs.guice) + implementation("com.google.code.gson:gson:2.13.2") + implementation(projects.api.attr) + implementation(projects.api.player) + implementation(projects.api.registry) + implementation(projects.api.repo) + implementation(projects.api.script) + implementation(projects.engine.events) + implementation(projects.engine.game) + implementation(projects.engine.map) + implementation(projects.engine.module) + implementation(projects.engine.plugin) + implementation(projects.engine.routefinder) +} diff --git a/api/poh/src/main/kotlin/org/rsmod/api/poh/PohAttributes.kt b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohAttributes.kt new file mode 100644 index 000000000..7c9012cf6 --- /dev/null +++ b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohAttributes.kt @@ -0,0 +1,94 @@ +package org.rsmod.api.poh + +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.player.stat.baseConstructionLvl +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.game.entity.Player + +/** + * House layout persistence through the `character_attrs` JSON path. + * - [ROOMS]: `"__" -> roomTypeIndex or (rotation shl 16)` + * - [FURNITURE]: `"___" -> furniture loc id` + * + * Room type and hotspot indices are the stable indices of the datagen resources, guarded by + * `poh_index_manifest.json`. House size / style / location / servant state live in the real varbits + * and persist through varp persistence - they are never duplicated here. + */ +public object PohAttributes { + public val ROOMS: AttributeKey> = + AttributeKey(persistenceKey = "poh_rooms") + + public val FURNITURE: AttributeKey> = + AttributeKey(persistenceKey = "poh_furniture") + + /** + * Packed [org.rsmod.map.CoordGrid] to relocate to on the next login (set on in-house logout). + */ + public val LOGIN_EXIT_COORD: AttributeKey = + AttributeKey(persistenceKey = "poh_login_exit_coord") + + private const val ROTATION_SHIFT: Int = 16 + private const val ROOM_INDEX_MASK: Int = (1 shl ROTATION_SHIFT) - 1 + + public fun encodeRoom(roomTypeIndex: Int, rotation: Int): Int = + roomTypeIndex or (rotation shl ROTATION_SHIFT) + + public fun decodeRoomIndex(value: Int): Int = value and ROOM_INDEX_MASK + + public fun decodeRoomRotation(value: Int): Int = (value shr ROTATION_SHIFT) and 0x3 + + /** Decodes the persisted house layout, or `null` when the player owns no house. */ + public fun load(player: Player, dataStore: PohDataStore): PohHouse? { + val location = player.pohHouseLocation + if (location == 0) { + return null + } + val house = + PohHouse( + size = PohConstants.gridSize(player.constructionLevelForHouse()), + style = player.pohHouseStyle, + location = location, + ) + val rooms = player.attr[ROOMS].orEmpty() + for ((key, value) in rooms) { + val slot = PohRoomSlot.fromPersistenceKey(key) ?: continue + val room = dataStore.room(decodeRoomIndex(value)) + house.rooms[slot] = PohRoom(room.key, decodeRoomRotation(value)) + } + val furniture = player.attr[FURNITURE].orEmpty() + for ((key, value) in furniture) { + val slot = PohHotspotSlot.fromPersistenceKey(key) ?: continue + house.furniture[slot] = RSCM.getReverseMapping(RSCMType.LOC, value) + } + return house + } + + /** Writes the full house layout back into the persistent attribute blobs. */ + public fun save(player: Player, house: PohHouse, dataStore: PohDataStore) { + val rooms = mutableMapOf() + for ((slot, room) in house.rooms) { + val type = dataStore.room(room.type) + rooms[slot.persistenceKey()] = encodeRoom(type.index, room.rotation) + } + player.attr[ROOMS] = rooms + + val furniture = mutableMapOf() + for ((slot, loc) in house.furniture) { + furniture[slot.persistenceKey()] = loc.asRSCM(RSCMType.LOC) + } + player.attr[FURNITURE] = furniture + } +} + +public var Player.pohHouseLocation: Int by intVarBit("varbit.poh_house_location") +public var Player.pohHouseStyle: Int by intVarBit("varbit.poh_house_style") +public var Player.pohHouseSize: Int by intVarBit("varbit.poh_house_size") +public var Player.pohBuildingMode: Int by intVarBit("varbit.poh_building_mode") +public var Player.pohServantType: Int by intVarBit("varbit.poh_servant_type") +public var Player.pohServantPay: Int by intVarBit("varbit.poh_servant_pay") +public var Player.pohDoorsOption: Int by intVarBit("varbit.poh_doors_option") + +internal fun Player.constructionLevelForHouse(): Int = baseConstructionLvl diff --git a/api/poh/src/main/kotlin/org/rsmod/api/poh/PohData.kt b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohData.kt new file mode 100644 index 000000000..bb6f0c0f9 --- /dev/null +++ b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohData.kt @@ -0,0 +1,424 @@ +package org.rsmod.api.poh + +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import jakarta.inject.Singleton + +/** Destination level a room occupies inside the house region. */ +public enum class PohFloor(public val destLevel: Int) { + DUNGEON(0), + GROUND(1), + UPPER(2), +} + +/** Open room edges; ids match the loc rotation baked into the template door hotspots. */ +public enum class PohDoorEdge(public val id: Int) { + WEST(0), + NORTH(1), + EAST(2), + SOUTH(3); + + /** The edge as seen from the room on the other side of the wall. */ + public val opposite: PohDoorEdge + get() = + when (this) { + WEST -> EAST + NORTH -> SOUTH + EAST -> WEST + SOUTH -> NORTH + } + + /** Rotates this edge by [rotation] quarter-turns clockwise. */ + public fun rotate(rotation: Int): PohDoorEdge = entries[(id + rotation) and 0x3] + + /** Grid delta towards the adjacent room across this edge. */ + public val deltaX: Int + get() = + when (this) { + WEST -> -1 + EAST -> 1 + else -> 0 + } + + public val deltaZ: Int + get() = + when (this) { + SOUTH -> -1 + NORTH -> 1 + else -> 0 + } +} + +/** One of the 13 house style slots on the template map squares. */ +public data class PohStyle( + val index: Int, + val key: String, + val region: Int, + val extraRegion: Int, + val level: Int, + val doorLeftLoc: String, + val doorRightLoc: String, +) { + /** The style's real openable doors and wall filler shown outside building mode. */ + val doors: PohStyleDoors + get() = PohStyleDoors.forStyle(key) +} + +/** + * The real door, wall, and window locs a style swaps in for its template locs: open passages + * between rooms get the closed door pair (captures: `civitas_poh_door_l/r` in rsprox-289/294), + * walled-off door edges get the style's plain wall piece, and the `poh_dynamic_window` placeholders + * baked into the room templates get the style's real wall window (Zenyte `refreshWindows`, applied + * in all modes). Loc names verified against the merged gamevals and `osrs-dumps/config/dump.loc`; + * the wilderness slot has no wall set of its own and borrows the basic-stone brick wall and window. + */ +public data class PohStyleDoors( + val closedLeft: String, + val closedRight: String, + val openLeft: String, + val openRight: String, + val wallFiller: String, + val window: String, +) { + public companion object { + private val STYLE_DOORS: Map = + mapOf( + "rimmington" to + PohStyleDoors( + closedLeft = "loc.village_door_l", + closedRight = "loc.village_door_r", + openLeft = "loc.village_door_l_open", + openRight = "loc.village_door_r_open", + wallFiller = "loc.village_wall", + window = "loc.village_wall_window", + ), + "lumbridge" to + PohStyleDoors( + closedLeft = "loc.poordoor_double_inner", + closedRight = "loc.poordoor_doubler_inner", + openLeft = "loc.openpoordoor_double_inner", + openRight = "loc.openpoordoor_doubler_inner", + wallFiller = "loc.brickwall_nosharelight", + window = "loc.brickwall_window", + ), + "pollnivneach" to + PohStyleDoors( + closedLeft = "loc.desert_door_l", + closedRight = "loc.desert_door_r", + openLeft = "loc.desert_door_l_open", + openRight = "loc.desert_door_r_open", + wallFiller = "loc.desertwall_nosharelight", + window = "loc.desert_wall_window", + ), + "rellekka" to + PohStyleDoors( + closedLeft = "loc.rellekka_poh_doubledoorl", + closedRight = "loc.rellekka_poh_doubledoor", + openLeft = "loc.rellekka_poh_doubledoorl_open", + openRight = "loc.rellekka_poh_doubledoor_open", + wallFiller = "loc.viking_longhall_wall_inner", + window = "loc.viking_longhall_wall_window_inner", + ), + "brimhaven" to + PohStyleDoors( + closedLeft = "loc.timberwall_doorl", + closedRight = "loc.timberwall_door", + openLeft = "loc.timberwall_doorl_open", + openRight = "loc.timberwall_door_open", + wallFiller = "loc.timberwall", + window = "loc.timberwall_with_window_2", + ), + "yanille" to + PohStyleDoors( + closedLeft = "loc.yanille_poh_double_doorl", + closedRight = "loc.yanille_poh_double_door", + openLeft = "loc.yanille_poh_double_doorl_open", + openRight = "loc.yanille_poh_double_door_open", + wallFiller = "loc.yanille_poh_wall", + window = "loc.yanille_poh_wall_window", + ), + "deathly" to + PohStyleDoors( + closedLeft = "loc.deathly_poh_double_doorl", + closedRight = "loc.deathly_poh_double_door", + openLeft = "loc.deathly_poh_double_doorl_open", + openRight = "loc.deathly_poh_double_door_open", + wallFiller = "loc.deathly_poh_wall", + window = "loc.deathly_poh_wall_window", + ), + "twisted" to + PohStyleDoors( + closedLeft = "loc.twisted_poh_doubledoorl", + closedRight = "loc.twisted_poh_doubledoor", + openLeft = "loc.twisted_poh_doubledoorl_open", + openRight = "loc.twisted_poh_doubledoor_open", + wallFiller = "loc.twisted_poh_wall_inner", + window = "loc.twisted_poh_wall_window_inner", + ), + "hosidius" to + PohStyleDoors( + closedLeft = "loc.hosidius_poh_doubledoorl", + closedRight = "loc.hosidius_poh_doubledoor", + openLeft = "loc.hosidius_poh_doubledoorl_open", + openRight = "loc.hosidius_poh_doubledoor_open", + wallFiller = "loc.hosidius_poh_wall", + window = "loc.hosidius_poh_wall_window", + ), + "xmas2020" to + PohStyleDoors( + closedLeft = "loc.xmas2020_poh_doubledoorl", + closedRight = "loc.xmas2020_poh_doubledoor", + openLeft = "loc.xmas2020_poh_doubledoorl_open", + openRight = "loc.xmas2020_poh_doubledoor_open", + wallFiller = "loc.xmas2020_poh_wall", + window = "loc.xmas2020_poh_wall_window", + ), + "civitas" to + PohStyleDoors( + closedLeft = "loc.civitas_poh_door_l", + closedRight = "loc.civitas_poh_door_r", + openLeft = "loc.civitas_poh_door_l_open", + openRight = "loc.civitas_poh_door_r_open", + wallFiller = "loc.civitas_poh_wall_default", + window = "loc.civitas_poh_wall_window", + ), + "canifis" to + PohStyleDoors( + closedLeft = "loc.canifis_poh_doubledoorl", + closedRight = "loc.canifis_poh_doubledoor", + openLeft = "loc.canifis_poh_doubledoorl_open", + openRight = "loc.canifis_poh_doubledoor_open", + wallFiller = "loc.canifis_poh_wall_plain", + window = "loc.canifis_poh_wall_window_inner", + ), + "wilderness" to + PohStyleDoors( + closedLeft = "loc.wild_doubledoor_l", + closedRight = "loc.wild_doubledoor_r", + openLeft = "loc.wild_doubledoor_open_l", + openRight = "loc.wild_doubledoor_open_r", + wallFiller = "loc.brickwall_nosharelight", + window = "loc.brickwall_window", + ), + ) + + public fun forStyle(key: String): PohStyleDoors = + STYLE_DOORS[key] ?: error("No door set for house style '$key'.") + + /** Every closed/open door loc of every style, for door-interaction scripts. */ + public fun allDoorLocs(): Sequence = STYLE_DOORS.values.asSequence() + } +} + +/** Which of the doorl/doorr pair a template door loc is (naming varies slightly per style). */ +public enum class PohDoorSide { + LEFT, + RIGHT, +} + +/** A door-hotspot wall loc baked into a room template zone. */ +public data class PohDoorPlacement( + val edge: PohDoorEdge, + val side: PohDoorSide, + val localX: Int, + val localZ: Int, + val rotation: Int, +) + +/** One of the 34 room templates. */ +public data class PohRoomType( + val index: Int, + val key: String, + val templateRegion: Int, + val templateZoneX: Int, + val templateZoneZ: Int, + val doorEdges: Set, + val doorPlacements: List, + val floor: PohFloor, +) { + /** Extra-square rooms live on the z-row-111 template squares (`style.extraRegion`). */ + val extraSquare: Boolean + get() = templateRegion == EXTRA_TEMPLATE_REGION + + public companion object { + public const val MAIN_TEMPLATE_REGION: Int = 7534 + public const val EXTRA_TEMPLATE_REGION: Int = 7535 + } +} + +/** A single buildable hotspot placement within a room template. */ +public data class PohHotspot( + val index: Int, + val loc: String, + val roomKey: String, + val name: String, + val localX: Int, + val localZ: Int, + val level: Int, + val shape: Int, + val rotation: Int, +) + +/** A `poh_dynamic_window` placeholder placement within a room template (style-0 anchored). */ +public data class PohWindowPlacement( + val roomKey: String, + val localX: Int, + val localZ: Int, + val shape: Int, + val rotation: Int, +) + +/** + * Loads the datagen resources (`poh_rooms.json`, `poh_hotspots.json`) committed by + * `:tools:poh-datagen:run` and verifies them against `poh_index_manifest.json`. + * + * Room and hotspot **indices are persisted** in `character_attrs` blobs, so the manifest check + * guarantees a regenerated table can never silently reinterpret saved houses. + */ +@Singleton +public class PohDataStore { + public val styles: List + public val rooms: List + public val hotspots: List + public val windows: List + + private val roomsByKey: Map + private val hotspotsByRoom: Map> + private val windowsByRoom: Map> + + init { + val roomsJson = parseResource("/poh_rooms.json") + val hotspotsJson = parseResource("/poh_hotspots.json") + val windowsJson = parseResource("/poh_windows.json") + val manifest = parseResource("/poh_index_manifest.json") + + styles = + roomsJson.getAsJsonArray("styles").map { el -> + val obj = el.asJsonObject + PohStyle( + index = obj["index"].asInt, + key = obj["key"].asString, + region = obj["region"].asInt, + extraRegion = obj["extraRegion"].asInt, + level = obj["level"].asInt, + doorLeftLoc = obj["doorLeftLoc"].asString, + doorRightLoc = obj["doorRightLoc"].asString, + ) + } + + rooms = + roomsJson.getAsJsonArray("rooms").map { el -> + val obj = el.asJsonObject + PohRoomType( + index = obj["index"].asInt, + key = obj["roomKey"].asString, + templateRegion = obj["templateRegion"].asInt, + templateZoneX = obj["templateZoneX"].asInt, + templateZoneZ = obj["templateZoneZ"].asInt, + doorEdges = + obj.getAsJsonArray("doorEdges") + .map { PohDoorEdge.valueOf(it.asString) } + .toSet(), + doorPlacements = + obj.getAsJsonArray("doorPlacements").map { doorEl -> + val door = doorEl.asJsonObject + PohDoorPlacement( + edge = PohDoorEdge.valueOf(door["edge"].asString), + side = PohDoorSide.valueOf(door["side"].asString), + localX = door["localX"].asInt, + localZ = door["localZ"].asInt, + rotation = door["rotation"].asInt, + ) + }, + floor = PohFloor.valueOf(obj["floor"].asString), + ) + } + + hotspots = + hotspotsJson.getAsJsonArray("hotspots").map { el -> + val obj = el.asJsonObject + PohHotspot( + index = obj["index"].asInt, + loc = obj["hotspotLoc"].asString, + roomKey = obj["roomKey"].asString, + name = obj["name"].asString, + localX = obj["localX"].asInt, + localZ = obj["localZ"].asInt, + level = obj["level"].asInt, + shape = obj["shape"].asInt, + rotation = obj["rotation"].asInt, + ) + } + + windows = + windowsJson.getAsJsonArray("windows").map { el -> + val obj = el.asJsonObject + PohWindowPlacement( + roomKey = obj["roomKey"].asString, + localX = obj["localX"].asInt, + localZ = obj["localZ"].asInt, + shape = obj["shape"].asInt, + rotation = obj["rotation"].asInt, + ) + } + + verifyManifest(manifest) + + roomsByKey = rooms.associateBy { it.key } + hotspotsByRoom = hotspots.groupBy { it.roomKey } + windowsByRoom = windows.groupBy { it.roomKey } + } + + public fun style(index: Int): PohStyle = + styles.getOrNull(index) ?: error("Invalid poh style index: $index") + + public fun room(key: String): PohRoomType = + roomsByKey[key] ?: error("Invalid poh room key: $key") + + public fun roomOrNull(key: String): PohRoomType? = roomsByKey[key] + + public fun room(index: Int): PohRoomType = + rooms.getOrNull(index) ?: error("Invalid poh room index: $index") + + public fun hotspot(index: Int): PohHotspot = + hotspots.getOrNull(index) ?: error("Invalid poh hotspot index: $index") + + public fun hotspots(roomKey: String): List = hotspotsByRoom[roomKey].orEmpty() + + public fun windows(roomKey: String): List = windowsByRoom[roomKey].orEmpty() + + private fun verifyManifest(manifest: JsonObject) { + val manifestRooms = manifest.getAsJsonArray("rooms").map { it.asString } + val manifestHotspots = manifest.getAsJsonArray("hotspots").map { it.asString } + + check(manifestRooms.size == rooms.size) { + "poh_index_manifest.json rooms (${manifestRooms.size}) out of sync with " + + "poh_rooms.json (${rooms.size}); regenerate via :tools:poh-datagen:run." + } + for ((i, room) in rooms.withIndex()) { + check(manifestRooms[i] == room.key) { + "Room index $i identity mismatch: manifest='${manifestRooms[i]}', data='${room.key}'." + } + } + + check(manifestHotspots.size == hotspots.size) { + "poh_index_manifest.json hotspots (${manifestHotspots.size}) out of sync with " + + "poh_hotspots.json (${hotspots.size}); regenerate via :tools:poh-datagen:run." + } + for ((i, hotspot) in hotspots.withIndex()) { + val identity = + "${hotspot.roomKey}|${hotspot.loc}|${hotspot.localX}|${hotspot.localZ}|${hotspot.level}" + check(manifestHotspots[i] == identity) { + "Hotspot index $i identity mismatch: manifest='${manifestHotspots[i]}', data='$identity'." + } + } + } + + private fun parseResource(path: String): JsonObject { + val stream = + checkNotNull(PohDataStore::class.java.getResourceAsStream(path)) { + "Missing poh resource: $path (run `./gradlew :tools:poh-datagen:run`)." + } + return stream.reader().use { JsonParser.parseReader(it).asJsonObject } + } +} diff --git a/api/poh/src/main/kotlin/org/rsmod/api/poh/PohEvents.kt b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohEvents.kt new file mode 100644 index 000000000..be02173bc --- /dev/null +++ b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohEvents.kt @@ -0,0 +1,14 @@ +package org.rsmod.api.poh + +import org.rsmod.events.UnboundEvent +import org.rsmod.game.entity.Player + +/** + * Published after the owner is teleported into their (re)built house region. Feature scripts + * subscribe to (re)spawn house npcs - servants, dungeon guards - idempotently. + */ +public data class PohHouseEnteredEvent(public val player: Player, public val buildMode: Boolean) : + UnboundEvent + +/** Published when the owner leaves their house (exit portal, options panel or logout). */ +public data class PohHouseExitedEvent(public val player: Player) : UnboundEvent diff --git a/api/poh/src/main/kotlin/org/rsmod/api/poh/PohHouse.kt b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohHouse.kt new file mode 100644 index 000000000..76e5a2bdf --- /dev/null +++ b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohHouse.kt @@ -0,0 +1,151 @@ +package org.rsmod.api.poh + +import org.rsmod.map.CoordGrid + +/** + * A room position within the house: [level] is the region destination level (0 = dungeon, 1 = + * ground floor, 2 = upper floor) and [gridX]/[gridZ] address the 7x7 room grid. + */ +public data class PohRoomSlot(val level: Int, val gridX: Int, val gridZ: Int) { + public fun translate(edge: PohDoorEdge): PohRoomSlot = + PohRoomSlot(level, gridX + edge.deltaX, gridZ + edge.deltaZ) + + public fun persistenceKey(): String = "${level}_${gridX}_$gridZ" + + public companion object { + public fun fromPersistenceKey(key: String): PohRoomSlot? { + val parts = key.split('_') + if (parts.size != 3) return null + val (level, gridX, gridZ) = parts.map { it.toIntOrNull() ?: return null } + return PohRoomSlot(level, gridX, gridZ) + } + } +} + +/** A built room: [type] is a [PohRoomType.key]; [rotation] is quarter-turns clockwise (0-3). */ +public data class PohRoom(val type: String, val rotation: Int) + +/** A furniture position: the room slot plus the [PohHotspot.index] within that room's template. */ +public data class PohHotspotSlot( + val level: Int, + val gridX: Int, + val gridZ: Int, + val hotspotIndex: Int, +) { + public val roomSlot: PohRoomSlot + get() = PohRoomSlot(level, gridX, gridZ) + + public fun persistenceKey(): String = "${level}_${gridX}_${gridZ}_$hotspotIndex" + + public companion object { + public fun fromPersistenceKey(key: String): PohHotspotSlot? { + val parts = key.split('_') + if (parts.size != 4) return null + val (level, gridX, gridZ, index) = parts.map { it.toIntOrNull() ?: return null } + return PohHotspotSlot(level, gridX, gridZ, index) + } + } +} + +/** + * The in-memory model of a player's house. [size], [style] and [location] mirror the real varbits + * (`poh_house_size`, `poh_house_style`, `poh_house_location`); rooms and furniture persist through + * [PohAttributes]. + */ +public class PohHouse( + public var size: Int, + public var style: Int, + public var location: Int, + public val rooms: MutableMap = mutableMapOf(), + public val furniture: MutableMap = mutableMapOf(), +) { + /** Grid cells outside the current buildable window reject room placement. */ + public fun inBuildableArea(slot: PohRoomSlot): Boolean { + val start = (PohConstants.GRID_LENGTH - size) / 2 + val end = start + size - 1 + return slot.gridX in start..end && slot.gridZ in start..end + } + + public fun furnitureAt(slot: PohRoomSlot): Map = + furniture.filterKeys { it.roomSlot == slot } +} + +/** The nine house locations; [varValue] is stored in `varbit.poh_house_location` (0 = no house). */ +public enum class PohLocation( + public val varValue: Int, + public val displayName: String, + public val exitCoords: CoordGrid, + public val relocateCost: Int, +) { + RIMMINGTON(1, "Rimmington", CoordGrid(2953, 3224, 0), 5_000), + TAVERLEY(2, "Taverley", CoordGrid(2893, 3465, 0), 5_000), + POLLNIVNEACH(3, "Pollnivneach", CoordGrid(3340, 3003, 0), 7_500), + RELLEKKA(4, "Rellekka", CoordGrid(2670, 3631, 0), 10_000), + BRIMHAVEN(5, "Brimhaven", CoordGrid(2757, 3178, 0), 15_000), + YANILLE(6, "Yanille", CoordGrid(2544, 3095, 0), 25_000), + KOUREND(7, "Hosidius", CoordGrid(1742, 3517, 0), 8_750), + PRIFDDINAS(8, "Prifddinas", CoordGrid(3239, 6079, 0), 25_000), + ALDARIN(9, "Aldarin", CoordGrid(1422, 2964, 0), 7_500); + + public companion object { + public fun forVarValue(value: Int): PohLocation? = + entries.firstOrNull { it.varValue == value } + } +} + +public object PohConstants { + /** The room grid is always addressed as 7x7; lower Construction levels shrink the window. */ + public const val GRID_LENGTH: Int = 7 + + public const val ZONE_TILE_LENGTH: Int = 8 + + /** + * The client renders a 13x13-zone build area centred on the player's zone at rebuild time, so + * every zone a build area can reach from a walkable tile must be landscaped or its height cliff + * at the mapped/unmapped boundary shows inside the draw distance. A 16x16 small-region slot + * cannot cover that (16 > 13 leaves bare corners); houses therefore allocate a 40x40 + * large-region slot, centre the grid, and landscape the lawn ring plus six zones on every + * side - the maximum reach of a build area centred anywhere on the walkable area. + */ + public const val BUILD_AREA_ZONE_RADIUS: Int = 6 + + /** + * Region zone of room grid cell (0, 0). Centres the 7x7 grid plus its one-zone lawn ring (9 + * zones) inside the 40-zone large-region slot: rooms at zones 16-22, lawn ring at 15 and 23. + */ + public const val GRID_ZONE_OFFSET: Int = 16 + + /** Landscaped zone range: the lawn ring extended by the build-area reach on every side. */ + public const val LANDSCAPE_MIN_ZONE: Int = GRID_ZONE_OFFSET - 1 - BUILD_AREA_ZONE_RADIUS + public const val LANDSCAPE_MAX_ZONE: Int = + GRID_ZONE_OFFSET + GRID_LENGTH + BUILD_AREA_ZONE_RADIUS + + /** Region-local tile bounds of the walkable house area (rooms plus the lawn ring). */ + public const val WALKABLE_MIN_TILE: Int = (GRID_ZONE_OFFSET - 1) * ZONE_TILE_LENGTH + public const val WALKABLE_MAX_TILE: Int = + (GRID_ZONE_OFFSET + GRID_LENGTH + 1) * ZONE_TILE_LENGTH - 1 + + public const val HOUSE_COST: Int = 1_000 + + public const val EXIT_PORTAL_LOC: String = "loc.poh_exit_portal" + + /** Garden centrepiece hotspot loc; the exit portal is built here on house creation. */ + public const val GARDEN_CENTREPIECE_LOC: String = "loc.poh_crude_garden_1" + + /** Buildable grid dimension for a Construction level: 3x3 at 1 up to 7x7 at 60+. */ + public fun gridSize(constructionLevel: Int): Int = + when { + constructionLevel >= 60 -> 7 + constructionLevel >= 45 -> 6 + constructionLevel >= 30 -> 5 + constructionLevel >= 15 -> 4 + else -> 3 + } + + private val ROOM_COUNT_GATES = + intArrayOf(26, 32, 38, 44, 50, 56, 62, 68, 74, 80, 86, 92, 96, 99) + + /** Maximum number of rooms for a Construction level: 20 at level 1 up to 34 at 99. */ + public fun maxRooms(constructionLevel: Int): Int = + 20 + ROOM_COUNT_GATES.count { constructionLevel >= it } +} diff --git a/api/poh/src/main/kotlin/org/rsmod/api/poh/PohManager.kt b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohManager.kt new file mode 100644 index 000000000..4955e6e8a --- /dev/null +++ b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohManager.kt @@ -0,0 +1,801 @@ +package org.rsmod.api.poh + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.ObjectServerType +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.registry.region.RegionRegistry +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.repo.region.RegionRepository +import org.rsmod.events.EventBus +import org.rsmod.game.entity.Player +import org.rsmod.game.entity.util.PathingEntityCommon +import org.rsmod.game.loc.LocAngle +import org.rsmod.game.loc.LocEntity +import org.rsmod.game.loc.LocInfo +import org.rsmod.game.loc.LocShape +import org.rsmod.game.map.collision.isWalkBlocked +import org.rsmod.game.map.collision.isZoneValid +import org.rsmod.game.region.Region +import org.rsmod.game.region.util.RegionRotations +import org.rsmod.map.CoordGrid +import org.rsmod.map.zone.ZoneGrid +import org.rsmod.routefinder.collision.CollisionFlagMap +import org.rsmod.routefinder.flag.CollisionFlag.BLOCK_WALK +import org.rsmod.routefinder.loc.LocLayerConstants +import org.rsmod.routefinder.util.Rotations + +/** + * Owns the lifecycle of player-owned house regions: one region per owner, allocated on [enter], + * reclaimed on [leave]/logout. Mirrors `InstanceManager`'s shape, but guests are out of scope - the + * region map is keyed by the owner's account id only. + */ +@Singleton +public class PohManager +@Inject +constructor( + private val regionRepo: RegionRepository, + private val locRepo: LocRepository, + private val dataStore: PohDataStore, + private val regionBuilder: PohRegionBuilder, + private val collision: CollisionFlagMap, + private val eventBus: EventBus, +) { + public class ActiveHouse( + public val region: Region, + public val house: PohHouse, + public val owner: Long, + ) + + public sealed class EnterResult { + public data class Success(val enterCoords: CoordGrid) : EnterResult() + + public data object NoHouse : EnterResult() + + public data object NoSpace : EnterResult() + } + + private val active = HashMap() + + public fun hasHouse(player: Player): Boolean = player.pohHouseLocation != 0 + + public fun activeHouse(player: Player): ActiveHouse? = active[player.ownerId()] + + public fun houseOf(player: Player): PohHouse? = + activeHouse(player)?.house ?: PohAttributes.load(player, dataStore) + + /** True while the player is standing inside their own house region. */ + public fun isInOwnHouse(player: Player): Boolean { + val current = activeHouse(player) ?: return false + return player.coords.x in current.region.southWest.x..current.region.northEast.x && + player.coords.z in current.region.southWest.z..current.region.northEast.z + } + + /** + * Creates a fresh Rimmington-style starter house: a Garden (with the exit portal built on its + * centrepiece) and a Parlour directly north of it. + */ + public fun createHouse(player: Player) { + check(!hasHouse(player)) { "Player already owns a house: ${player.username}" } + player.pohHouseLocation = PohLocation.RIMMINGTON.varValue + player.pohHouseStyle = 0 + val house = + PohHouse( + size = PohConstants.gridSize(player.constructionLevelForHouse()), + style = 0, + location = PohLocation.RIMMINGTON.varValue, + ) + val gardenSlot = PohRoomSlot(PohFloor.GROUND.destLevel, gridX = 3, gridZ = 3) + val parlourSlot = PohRoomSlot(PohFloor.GROUND.destLevel, gridX = 3, gridZ = 4) + house.rooms[gardenSlot] = PohRoom("garden", rotation = 0) + house.rooms[parlourSlot] = PohRoom("parlour", rotation = 0) + + val centrepiece = + dataStore.hotspots("garden").first { it.loc == PohConstants.GARDEN_CENTREPIECE_LOC } + house.furniture[ + PohHotspotSlot( + gardenSlot.level, + gardenSlot.gridX, + gardenSlot.gridZ, + centrepiece.index, + )] = PohConstants.EXIT_PORTAL_LOC + PohAttributes.save(player, house, dataStore) + } + + /** + * Allocates (or reuses) the player's house region and teleports them inside. Returns + * [EnterResult.NoHouse] when the player owns no house and [EnterResult.NoSpace] when region + * capacity is exhausted. + */ + public fun enter(player: Player, buildMode: Boolean): EnterResult { + val house = houseOf(player) ?: return EnterResult.NoHouse + house.size = PohConstants.gridSize(player.constructionLevelForHouse()) + + discard(player) + + val template = regionBuilder.buildTemplate(house) + val region = regionRepo.add(template) ?: return EnterResult.NoSpace + regionRepo.protect(region) + blockLawnBoundary(region, house) + + val activeHouse = ActiveHouse(region, house, player.ownerId()) + active[player.ownerId()] = activeHouse + + player.pohBuildingMode = if (buildMode) 1 else 0 + player.pohHouseSize = house.size - 3 + refresh(activeHouse, buildMode) + + val enterCoords = enterCoords(activeHouse) + PathingEntityCommon.telejump(player, collision, enterCoords) + eventBus.publish(PohHouseEnteredEvent(player, buildMode)) + return EnterResult.Success(enterCoords) + } + + /** + * Drops the player's house region and returns the coordinates outside the house's world portal. + * The caller is responsible for the teleport when invoked from protected access. + */ + public fun leave(player: Player): CoordGrid { + val house = houseOf(player) + discard(player) + player.pohBuildingMode = 0 + eventBus.publish(PohHouseExitedEvent(player)) + val location = house?.location?.let(PohLocation::forVarValue) ?: PohLocation.RIMMINGTON + return location.exitCoords + } + + /** + * Called on logout: releases the region without teleporting. When the owner logs out inside + * their house, the saved coordinates would point into a reclaimed region slot, so the next + * login lands at the house's world portal instead (via [PohAttributes.LOGIN_EXIT_COORD]). + */ + public fun handleLogout(player: Player) { + if (active.containsKey(player.ownerId())) { + if (isInOwnHouse(player)) { + val location = + PohLocation.forVarValue(player.pohHouseLocation) ?: PohLocation.RIMMINGTON + player.attr[PohAttributes.LOGIN_EXIT_COORD] = location.exitCoords.packed + } + eventBus.publish(PohHouseExitedEvent(player)) + } + discard(player) + } + + /** + * Relocates a player who logged out inside their house to the world portal outside it. Also + * self-heals saves whose coordinates point into the region working area without a pending exit + * coordinate - hard server shutdowns skip the logout event, leaving raw in-region coordinates + * behind. + */ + public fun handleLogin(player: Player) { + val packed = player.attr[PohAttributes.LOGIN_EXIT_COORD] + if (packed != null) { + player.attr.remove(PohAttributes.LOGIN_EXIT_COORD) + PathingEntityCommon.telejump(player, collision, CoordGrid(packed)) + return + } + if (player.coords.x >= RegionRegistry.INSTANCE_MIN_X && activeHouse(player) == null) { + val location = + PohLocation.forVarValue(player.pohHouseLocation) ?: PohLocation.RIMMINGTON + PathingEntityCommon.telejump(player, collision, location.exitCoords) + } + } + + /** + * Rebuilds the house region from scratch after a structural change (room added/removed), + * keeping the player at the same relative coordinates. + */ + public fun rebuild(player: Player) { + val current = activeHouse(player) ?: return + val relative = player.coords - current.region.southWest + val buildMode = player.pohBuildingMode == 1 + + discard(player) + + val template = regionBuilder.buildTemplate(current.house) + val region = regionRepo.add(template) ?: return + regionRepo.protect(region) + blockLawnBoundary(region, current.house) + + val activeHouse = ActiveHouse(region, current.house, current.owner) + active[current.owner] = activeHouse + refresh(activeHouse, buildMode) + + val dest = + CoordGrid( + region.southWest.x + relative.x, + region.southWest.z + relative.z, + player.coords.level, + ) + PathingEntityCommon.telejump(player, collision, dest) + eventBus.publish(PohHouseEnteredEvent(player, buildMode)) + } + + /** Reapplies furniture, hotspot visibility and open passages in the current region. */ + public fun refresh(player: Player) { + val current = activeHouse(player) ?: return + refresh(current, buildMode = player.pohBuildingMode == 1) + } + + /** Persists the current house layout into the player's attribute blobs. */ + public fun persist(player: Player) { + val house = activeHouse(player)?.house ?: return + PohAttributes.save(player, house, dataStore) + } + + /** Builds [furnitureLoc] on [slot], replacing the ghost hotspot loc client-side. */ + public fun applyFurniture(player: Player, slot: PohHotspotSlot, furnitureLoc: String) { + val current = activeHouse(player) ?: return + val room = current.house.rooms[slot.roomSlot] ?: return + val hotspot = dataStore.hotspot(slot.hotspotIndex) + current.house.furniture[slot] = furnitureLoc + + val coords = hotspotCoords(current.region, slot.roomSlot, room, hotspot) + val angle = (hotspot.rotation + room.rotation) and 0x3 + locRepo.add(coords, furnitureLoc, Int.MAX_VALUE, LocAngle[angle], LocShape[hotspot.shape]) + PohAttributes.save(player, current.house, dataStore) + } + + /** + * Removes the furniture on [slot] and adds the ghost hotspot loc back - live implements removal + * as an *add* of the hotspot loc, never a bare delete. + */ + public fun removeFurniture(player: Player, slot: PohHotspotSlot) { + val current = activeHouse(player) ?: return + val room = current.house.rooms[slot.roomSlot] ?: return + val hotspot = dataStore.hotspot(slot.hotspotIndex) + current.house.furniture.remove(slot) + + val coords = hotspotCoords(current.region, slot.roomSlot, room, hotspot) + // Restoring the baked map loc: template-space angle, the region registry rotates it. + locRepo.add( + coords, + hotspot.loc, + Int.MAX_VALUE, + LocAngle[hotspot.rotation], + LocShape[hotspot.shape], + ) + PohAttributes.save(player, current.house, dataStore) + } + + /** Adds [room] at [slot] and rebuilds the region. */ + public fun addRoom(player: Player, slot: PohRoomSlot, room: PohRoom) { + val current = activeHouse(player) ?: return + current.house.rooms[slot] = room + PohAttributes.save(player, current.house, dataStore) + rebuild(player) + } + + /** Removes the room at [slot] (and its furniture) and rebuilds the region. */ + public fun removeRoom(player: Player, slot: PohRoomSlot) { + val current = activeHouse(player) ?: return + current.house.rooms.remove(slot) + current.house.furniture.keys.removeAll { it.roomSlot == slot } + PohAttributes.save(player, current.house, dataStore) + rebuild(player) + } + + /** Toggles building mode and refreshes hotspot visibility. */ + public fun setBuildingMode(player: Player, enabled: Boolean) { + player.pohBuildingMode = if (enabled) 1 else 0 + refresh(player) + } + + /* Coordinate helpers */ + + public fun roomSlotAt(region: Region, coords: CoordGrid): PohRoomSlot? { + val zoneX = (coords.x - region.southWest.x) / PohConstants.ZONE_TILE_LENGTH + val zoneZ = (coords.z - region.southWest.z) / PohConstants.ZONE_TILE_LENGTH + val gridX = zoneX - PohConstants.GRID_ZONE_OFFSET + val gridZ = zoneZ - PohConstants.GRID_ZONE_OFFSET + if (gridX !in 0 until PohConstants.GRID_LENGTH) return null + if (gridZ !in 0 until PohConstants.GRID_LENGTH) return null + return PohRoomSlot(coords.level, gridX, gridZ) + } + + public fun roomBaseCoords(region: Region, slot: PohRoomSlot): CoordGrid = + CoordGrid( + region.southWest.x + + (slot.gridX + PohConstants.GRID_ZONE_OFFSET) * PohConstants.ZONE_TILE_LENGTH, + region.southWest.z + + (slot.gridZ + PohConstants.GRID_ZONE_OFFSET) * PohConstants.ZONE_TILE_LENGTH, + slot.level, + ) + + /** + * Stamps a blocking ring on the outermost tile row of the lawn ring so players can't wander + * into the visual lawn padding (real houses fence you into the buildable area, and the padding + * exists only to keep the map-edge height cliff outside the draw distance). The dungeon level + * is additionally blocked wherever no dungeon room exists: its filler zones are invisible + * height spacers with no floor to stand on. + */ + private fun blockLawnBoundary(region: Region, house: PohHouse) { + val min = PohConstants.WALKABLE_MIN_TILE + val max = PohConstants.WALKABLE_MAX_TILE + for (level in PohFloor.DUNGEON.destLevel..PohFloor.UPPER.destLevel) { + for (t in min..max) { + collision.add(region.southWest.x + min, region.southWest.z + t, level, BLOCK_WALK) + collision.add(region.southWest.x + max, region.southWest.z + t, level, BLOCK_WALK) + collision.add(region.southWest.x + t, region.southWest.z + min, level, BLOCK_WALK) + collision.add(region.southWest.x + t, region.southWest.z + max, level, BLOCK_WALK) + } + } + for (gridX in 0 until PohConstants.GRID_LENGTH) { + for (gridZ in 0 until PohConstants.GRID_LENGTH) { + val slot = PohRoomSlot(PohFloor.DUNGEON.destLevel, gridX, gridZ) + if (house.rooms.containsKey(slot)) { + continue + } + val base = roomBaseCoords(region, slot) + for (dx in 0 until PohConstants.ZONE_TILE_LENGTH) { + for (dz in 0 until PohConstants.ZONE_TILE_LENGTH) { + collision.add(base.x + dx, base.z + dz, slot.level, BLOCK_WALK) + } + } + } + } + } + + public fun hotspotCoords( + region: Region, + slot: PohRoomSlot, + room: PohRoom, + hotspot: PohHotspot, + ): CoordGrid { + val type = locType(hotspot.loc) + val width = Rotations.rotate(hotspot.rotation, type.width, type.length) + val length = Rotations.rotate(hotspot.rotation, type.length, type.width) + val translation = + RegionRotations.translateLoc( + room.rotation, + ZoneGrid(hotspot.localX, hotspot.localZ, slot.level), + width, + length, + ) + return roomBaseCoords(region, slot).translate(translation) + } + + /** Resolves a clicked hotspot loc back to its [PohHotspotSlot]. */ + public fun findHotspot( + player: Player, + coords: CoordGrid, + locId: Int, + ): Pair? { + val current = activeHouse(player) ?: return null + val slot = roomSlotAt(current.region, coords) ?: return null + val room = current.house.rooms[slot] ?: return null + for (hotspot in dataStore.hotspots(room.type)) { + if (hotspot.loc.asRSCM(RSCMType.LOC) != locId) { + continue + } + if (hotspotCoords(current.region, slot, room, hotspot) == coords) { + val hotspotSlot = PohHotspotSlot(slot.level, slot.gridX, slot.gridZ, hotspot.index) + return hotspotSlot to hotspot + } + } + return null + } + + /** Resolves a clicked built-furniture loc back to its [PohHotspotSlot]. */ + public fun findBuiltFurniture( + player: Player, + coords: CoordGrid, + locId: Int, + ): Pair? { + val current = activeHouse(player) ?: return null + val slot = roomSlotAt(current.region, coords) ?: return null + val room = current.house.rooms[slot] ?: return null + for ((hotspotSlot, furnitureLoc) in current.house.furnitureAt(slot)) { + if (furnitureLoc.asRSCM(RSCMType.LOC) != locId) { + continue + } + val hotspot = dataStore.hotspot(hotspotSlot.hotspotIndex) + if (hotspotCoords(current.region, slot, room, hotspot) == coords) { + return hotspotSlot to furnitureLoc + } + } + return null + } + + /** Resolves a clicked door hotspot to the room slot and the (rotated) edge it faces. */ + public fun findDoorEdge(player: Player, coords: CoordGrid): Pair? { + val current = activeHouse(player) ?: return null + val slot = roomSlotAt(current.region, coords) ?: return null + val room = current.house.rooms[slot] ?: return null + val type = dataStore.room(room.type) + val base = roomBaseCoords(current.region, slot) + for (door in type.doorPlacements) { + val translation = + RegionRotations.translateCoords( + room.rotation, + ZoneGrid(door.localX, door.localZ, slot.level), + ) + if (base.translate(translation) == coords) { + return slot to door.edge.rotate(room.rotation) + } + } + return null + } + + /** + * Re-hides the template door-hotspot map loc at [coords]. Deleting a spawned loc removes the + * record that shadowed the baked map loc, resurrecting it — callers that despawn a real door + * (the door swing) must re-delete the hotspot underneath. + */ + public fun hideDoorHotspot(player: Player, coords: CoordGrid) { + val current = activeHouse(player) ?: return + val style = dataStore.style(current.house.style) + val slot = roomSlotAt(current.region, coords) ?: return + val room = current.house.rooms[slot] ?: return + val type = dataStore.room(room.type) + val base = roomBaseCoords(current.region, slot) + for (door in type.doorPlacements) { + val translation = + RegionRotations.translateCoords( + room.rotation, + ZoneGrid(door.localX, door.localZ, slot.level), + ) + if (base.translate(translation) == coords) { + deleteDoorLoc(coords, style, door.rotation) + return + } + } + } + + /* Region population */ + + private fun refresh(activeHouse: ActiveHouse, buildMode: Boolean) { + val house = activeHouse.house + val region = activeHouse.region + val style = dataStore.style(house.style) + + for ((slot, room) in house.rooms) { + refreshRoomHotspots(region, house, style, slot, room, buildMode) + refreshSharedPassages(region, house, style, slot, room, buildMode) + refreshWindows(region, style, slot, room) + } + ensureExitPortal(region, house) + } + + /** + * Swaps the `poh_dynamic_window` placeholders baked into the room templates for the style's + * real wall window, in every mode (Zenyte `refreshWindows`). + */ + private fun refreshWindows(region: Region, style: PohStyle, slot: PohRoomSlot, room: PohRoom) { + val base = roomBaseCoords(region, slot) + for (window in dataStore.windows(room.type)) { + val translation = + RegionRotations.translateCoords( + room.rotation, + ZoneGrid(window.localX, window.localZ, slot.level), + ) + val coords = base.translate(translation) + // Map-loc deletes match the template entity (the registry applies zone rotation). + val entity = + LocEntity( + WINDOW_PLACEHOLDER_LOC.asRSCM(RSCMType.LOC), + window.shape, + window.rotation, + ) + locRepo.del(LocInfo(LocLayerConstants.of(window.shape), coords, entity), Int.MAX_VALUE) + locRepo.add( + coords, + style.doors.window, + Int.MAX_VALUE, + LocAngle[(window.rotation + room.rotation) and 0x3], + LocShape[window.shape], + ) + } + } + + private fun refreshRoomHotspots( + region: Region, + house: PohHouse, + style: PohStyle, + slot: PohRoomSlot, + room: PohRoom, + buildMode: Boolean, + ) { + for (hotspot in dataStore.hotspots(room.type)) { + val hotspotSlot = PohHotspotSlot(slot.level, slot.gridX, slot.gridZ, hotspot.index) + val built = house.furniture[hotspotSlot] + val coords = hotspotCoords(region, slot, room, hotspot) + val regionAngle = (hotspot.rotation + room.rotation) and 0x3 + // Wall-embedded hotspots (chapel "Window space") leave a hole in the wall when bare + // deleted; Zenyte's removeHotspot swaps them to the style's wall piece instead. + val wallEmbedded = hotspot.shape == WALL_SHAPE && hotspot.name == WINDOW_SPACE_NAME + if (built != null) { + // Spawned locs carry region-space angles; the registry stores them verbatim. + locRepo.add( + coords, + built, + Int.MAX_VALUE, + LocAngle[regionAngle], + LocShape[hotspot.shape], + ) + } else if (buildMode) { + if (wallEmbedded) { + deleteSpawnedWallFiller(coords, style, regionAngle) + } + // Map-loc restores are matched against the *template* entity: the region registry + // applies the zone rotation itself, so the angle here is the unrotated one. + locRepo.add( + coords, + hotspot.loc, + Int.MAX_VALUE, + LocAngle[hotspot.rotation], + LocShape[hotspot.shape], + ) + } else { + // Same template-space rule for deletes; a rotated angle silently finds nothing. + val entity = + LocEntity(hotspot.loc.asRSCM(RSCMType.LOC), hotspot.shape, hotspot.rotation) + val loc = LocInfo(LocLayerConstants.of(hotspot.shape), coords, entity) + locRepo.del(loc, Int.MAX_VALUE) + if (wallEmbedded) { + locRepo.add( + coords, + style.doors.wallFiller, + Int.MAX_VALUE, + LocAngle[regionAngle], + LocShape[hotspot.shape], + ) + } + } + } + } + + /** Deletes a spawned wall filler left by a previous normal-mode window-space swap. */ + private fun deleteSpawnedWallFiller(coords: CoordGrid, style: PohStyle, angle: Int) { + val entity = LocEntity(style.doors.wallFiller.asRSCM(RSCMType.LOC), WALL_SHAPE, angle) + val loc = LocInfo(LocLayerConstants.of(WALL_SHAPE), coords, entity) + locRepo.del(loc, Int.MAX_VALUE) + } + + /** + * Manages the baked door-hotspot wall locs on every door edge of [room]. In building mode the + * template's clickable door hotspots are restored (clicking one adds/removes rooms). Outside + * building mode the hotspots are swapped for the style's real locs, as in the live captures + * (rsprox-289/294): edges shared with a room whose door edges allow passage get the closed door + * pair, walled-off edges get the style's wall filler, and exterior rooms (gardens, menagerie + * habitat) are left open. + */ + private fun refreshSharedPassages( + region: Region, + house: PohHouse, + style: PohStyle, + slot: PohRoomSlot, + room: PohRoom, + buildMode: Boolean, + ) { + val type = dataStore.room(room.type) + val base = roomBaseCoords(region, slot) + val exterior = room.type in EXTERIOR_ROOMS + for (door in type.doorPlacements) { + val rotatedEdge = door.edge.rotate(room.rotation) + val neighbor = house.rooms[slot.translate(rotatedEdge)] + val passage = + neighbor != null && + dataStore.room(neighbor.type).doorEdges.any { + it.rotate(neighbor.rotation) == rotatedEdge.opposite + } + val neighborExterior = neighbor != null && neighbor.type in EXTERIOR_ROOMS + val translation = + RegionRotations.translateCoords( + room.rotation, + ZoneGrid(door.localX, door.localZ, slot.level), + ) + val coords = base.translate(translation) + val regionAngle = (door.rotation + room.rotation) and 0x3 + if (buildMode) { + // Clear any real door or wall filler spawned by a previous normal-mode refresh, + // then restore the map-loc hotspot (template-space angle; the registry rotates). + deleteSpawnedDoorLoc(coords, style, regionAngle) + if (passage && !ownsSharedDoorway(slot, exterior, rotatedEdge, neighborExterior)) { + // A shared doorway keeps a single ghost pair, on the owning face; keep the + // other face's baked hotspot hidden so the doorway isn't doubled up. + deleteDoorLoc(coords, style, door.rotation) + continue + } + val hotspotLoc = + if (door.side == PohDoorSide.LEFT) style.doorLeftLoc else style.doorRightLoc + locRepo.add( + coords, + hotspotLoc, + Int.MAX_VALUE, + LocAngle[door.rotation], + LocShape[DOOR_SHAPE], + ) + } else { + deleteDoorLoc(coords, style, door.rotation) + // A doorway only gets one door pair, on the interior room's wall face: doors + // exist solely on the house's exterior boundary (garden <-> room). Interior + // rooms connect through open archways and garden edges stay open lawn. + val replacement = + when { + passage && !exterior && neighborExterior -> + if (door.side == PohDoorSide.LEFT) { + style.doors.closedLeft + } else { + style.doors.closedRight + } + passage -> null + exterior -> null + else -> style.doors.wallFiller + } ?: continue + locRepo.add( + coords, + replacement, + Int.MAX_VALUE, + LocAngle[regionAngle], + LocShape[DOOR_SHAPE], + ) + } + } + } + + /** + * Decides which face of a shared doorway hosts the single door pair / ghost hotspot pair. The + * interior room owns garden boundaries (where the real door lives outside building mode); + * same-kind pairs fall back to a deterministic slot ordering so exactly one side wins. + */ + private fun ownsSharedDoorway( + slot: PohRoomSlot, + exterior: Boolean, + rotatedEdge: PohDoorEdge, + neighborExterior: Boolean, + ): Boolean { + if (exterior != neighborExterior) { + return !exterior + } + val neighbor = slot.translate(rotatedEdge) + val ordering = + compareValuesBy( + slot, + neighbor, + PohRoomSlot::level, + PohRoomSlot::gridX, + PohRoomSlot::gridZ, + ) + return ordering < 0 + } + + /** Deletes the template's door-hotspot map loc at [coords] (template-space [angle]). */ + private fun deleteDoorLoc(coords: CoordGrid, style: PohStyle, angle: Int) { + for (doorLoc in listOf(style.doorLeftLoc, style.doorRightLoc)) { + val entity = LocEntity(doorLoc.asRSCM(RSCMType.LOC), DOOR_SHAPE, angle) + val loc = LocInfo(LocLayerConstants.of(DOOR_SHAPE), coords, entity) + if (locRepo.del(loc, Int.MAX_VALUE)) { + return + } + } + } + + /** Deletes a spawned real door (closed or open) or wall filler at [coords] (region angle). */ + private fun deleteSpawnedDoorLoc(coords: CoordGrid, style: PohStyle, angle: Int) { + val doors = style.doors + val spawned = + listOf( + doors.closedLeft, + doors.closedRight, + doors.openLeft, + doors.openRight, + doors.wallFiller, + ) + for (locName in spawned) { + val entity = LocEntity(locName.asRSCM(RSCMType.LOC), DOOR_SHAPE, angle) + val loc = LocInfo(LocLayerConstants.of(DOOR_SHAPE), coords, entity) + if (locRepo.del(loc, Int.MAX_VALUE)) { + return + } + } + } + + /** + * Guarantees the house has a way out: when no exit portal is built anywhere, one is placed on + * the first garden's centrepiece spot (without persisting it as furniture). + */ + private fun ensureExitPortal(region: Region, house: PohHouse) { + if (house.furniture.values.any { it == PohConstants.EXIT_PORTAL_LOC }) { + return + } + val (slot, room) = findGarden(house) ?: return + val centrepiece = gardenCentrepiece(room) ?: return + val coords = hotspotCoords(region, slot, room, centrepiece) + val angle = (centrepiece.rotation + room.rotation) and 0x3 + locRepo.add( + coords, + PohConstants.EXIT_PORTAL_LOC, + Int.MAX_VALUE, + LocAngle[angle], + LocShape[centrepiece.shape], + ) + } + + private fun findGarden(house: PohHouse): Pair? { + val entry = + house.rooms.entries.firstOrNull { it.value.type == "garden" } + ?: house.rooms.entries.firstOrNull { it.value.type == "superior_garden" } + ?: return null + return entry.key to entry.value + } + + private fun gardenCentrepiece(room: PohRoom): PohHotspot? = + dataStore.hotspots(room.type).firstOrNull { it.name == "Centrepiece space" } + + private fun enterCoords(activeHouse: ActiveHouse): CoordGrid { + val house = activeHouse.house + val garden = findGarden(house) + if (garden != null) { + val (slot, room) = garden + val translation = + RegionRotations.translateCoords( + room.rotation, + ZoneGrid(GARDEN_ENTER_X, GARDEN_ENTER_Z, slot.level), + ) + val coords = roomBaseCoords(activeHouse.region, slot).translate(translation) + return walkableNear(coords) + } + // No garden: fall back to the first ground-floor room's centre. + val slot = + house.rooms.keys.firstOrNull { it.level == PohFloor.GROUND.destLevel } + ?: house.rooms.keys.first() + return walkableNear(roomBaseCoords(activeHouse.region, slot).translate(3, 3)) + } + + private fun walkableNear(coords: CoordGrid): CoordGrid { + if (!isBlocked(coords)) { + return coords + } + for (radius in 1..3) { + for (dx in -radius..radius) { + for (dz in -radius..radius) { + val candidate = coords.translate(dx, dz) + if (!isBlocked(candidate)) { + return candidate + } + } + } + } + return coords + } + + private fun isBlocked(coords: CoordGrid): Boolean = + !collision.isZoneValid(coords) || collision.isWalkBlocked(coords) + + private fun discard(player: Player) { + val current = active.remove(player.ownerId()) ?: return + regionRepo.unprotect(current.region) + } + + private fun locType(loc: String): ObjectServerType { + val id = loc.asRSCM(RSCMType.LOC) + return ServerCacheManager.getObject(id) ?: error("Missing loc type: $loc") + } + + private fun Player.ownerId(): Long = checkNotNull(uuid) { "Player uuid not assigned: $this" } + + private companion object { + /** Door hotspots are straight-wall locs (shape 0). */ + private const val DOOR_SHAPE: Int = 0 + + /** Wall-embedded hotspots (chapel window spaces) share the straight-wall shape. */ + private const val WALL_SHAPE: Int = 0 + + /** Cache name of the chapel's decorated-window wall hotspots. */ + private const val WINDOW_SPACE_NAME: String = "Window space" + + /** Template placeholder swapped to the style's real wall window in every mode. */ + private const val WINDOW_PLACEHOLDER_LOC: String = "loc.poh_dynamic_window" + + /** Enter tile inside the garden, just south of the 2x2 centrepiece. */ + private const val GARDEN_ENTER_X: Int = 3 + private const val GARDEN_ENTER_Z: Int = 1 + + /** Rooms without full walls; their walled-off door edges stay open outside build mode. */ + private val EXTERIOR_ROOMS: Set = + setOf("garden", "formal_garden", "superior_garden", "menagerie_outdoor") + } +} diff --git a/api/poh/src/main/kotlin/org/rsmod/api/poh/PohRegionBuilder.kt b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohRegionBuilder.kt new file mode 100644 index 000000000..df90b83f5 --- /dev/null +++ b/api/poh/src/main/kotlin/org/rsmod/api/poh/PohRegionBuilder.kt @@ -0,0 +1,97 @@ +package org.rsmod.api.poh + +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.repo.region.RegionStaticTemplate +import org.rsmod.api.repo.region.RegionTemplate +import org.rsmod.map.zone.ZoneKey + +/** + * Turns a [PohHouse] into a [RegionStaticTemplate]. + * + * Destination levels: 0 = dungeon, 1 = ground floor, 2 = upper floor, 3 = roofs (reserved and + * unused - see [buildTemplate]). Each room's grid cell maps to region zone `(gridX + + * GRID_ZONE_OFFSET, gridZ + GRID_ZONE_OFFSET)` - the grid and its lawn ring are centred inside the + * 40x40 large-region slot, with the surrounding zones landscaped as visual padding. + * + * The per-zone setter is used deliberately: POH copies from the *style* source level into a *floor* + * destination level, which `RegionStaticTemplate.copy()` cannot express. + */ +@Singleton +public class PohRegionBuilder @Inject constructor(private val dataStore: PohDataStore) { + /** + * Builds the region template for [house]: one template zone per built room, plus grass fill for + * every remaining ground-floor zone. + * + * Roofs are deliberately omitted, so houses render open-topped - matching what live OSRS shows + * in building mode. The style roof palettes (template zones `(1,2)`/`(3,2)`/`(5,2)`, roomKeys + * `roof_a`/`roof_b`/`roof_c`) only hold complete 64-piece roof zones for the RS2-era styles; + * the OSRS-era styles (hosidius, xmas2020, deathly, twisted) carry stub palettes of ~4 locs, so + * a whole-zone roof copy cannot ship for all 13 styles. No map square in the rev-240 cache + * composes a multi-room roof either: the only placed roof pieces outside the palettes are the + * region-7257 showcase house's single verbatim `roof_a` zone copy over one isolated room (at + * room level + 1, not a fixed top level), and the `roof_b`/`roof_c` hip-corner pieces are never + * placed anywhere, leaving no evidenced piece-to-footprint mapping for adjacent rooms. + */ + public fun buildTemplate(house: PohHouse): RegionStaticTemplate { + val style = dataStore.style(house.style) + val grassZone = templateZone(style, dataStore.room("grass_filler")) + val hiddenZone = hiddenFillZone(style) + return RegionTemplate.createLarge { + for ((slot, room) in house.rooms) { + val type = dataStore.room(room.type) + val srcZone = templateZone(style, type) + val destX = slot.gridX + PohConstants.GRID_ZONE_OFFSET + val destZ = slot.gridZ + PohConstants.GRID_ZONE_OFFSET + when (room.rotation and 0x3) { + 0 -> this[destX, destZ, slot.level] = srcZone + 1 -> this[destX, destZ, slot.level] = srcZone.rotate90() + 2 -> this[destX, destZ, slot.level] = srcZone.rotate180() + 3 -> this[destX, destZ, slot.level] = srcZone.rotate270() + } + } + // Fill every remaining ground-level zone with lawn, and every remaining dungeon-level + // zone with an invisible height-only spacer zone. The under-fill is not cosmetic: the + // client stacks instance plane heights on the plane below, so a partially filled + // plane 0 raises only the ground rooms above dungeon rooms - a uniform plane-0 fill + // is what keeps the ground floor flat. The spacer zone renders nothing (no ground, no + // locs - the decorated rock zone's wall-top models poke through the lawn above), and + // the fill extends over every zone a 13x13 client build area can reach from a + // walkable tile, so the height cliff at the mapped/unmapped boundary is never + // rendered. Levels 2/3 stay unset. + for (zoneX in PohConstants.LANDSCAPE_MIN_ZONE..PohConstants.LANDSCAPE_MAX_ZONE) { + for (zoneZ in PohConstants.LANDSCAPE_MIN_ZONE..PohConstants.LANDSCAPE_MAX_ZONE) { + val gridX = zoneX - PohConstants.GRID_ZONE_OFFSET + val gridZ = zoneZ - PohConstants.GRID_ZONE_OFFSET + val ground = PohRoomSlot(PohFloor.GROUND.destLevel, gridX, gridZ) + if (!house.rooms.containsKey(ground)) { + this[zoneX, zoneZ, PohFloor.GROUND.destLevel] = grassZone + } + val dungeon = PohRoomSlot(PohFloor.DUNGEON.destLevel, gridX, gridZ) + if (!house.rooms.containsKey(dungeon)) { + this[zoneX, zoneZ, PohFloor.DUNGEON.destLevel] = hiddenZone + } + } + } + } + } + + /** + * An empty zone of the style's extra template square: explicit flat heights (the same 240 units + * per level as every template zone) but no underlays, overlays, or locs - it contributes + * stacking height while rendering as black void. + */ + private fun hiddenFillZone(style: PohStyle): ZoneKey { + val baseZoneX = (style.extraRegion shr 8) * PohConstants.ZONE_TILE_LENGTH + val baseZoneZ = (style.extraRegion and 0xFF) * PohConstants.ZONE_TILE_LENGTH + return ZoneKey(baseZoneX, baseZoneZ, style.level) + } + + /** Resolves the normal-map zone holding [room]'s template for [style]. */ + public fun templateZone(style: PohStyle, room: PohRoomType): ZoneKey { + val region = if (room.extraSquare) style.extraRegion else style.region + val baseZoneX = (region shr 8) * PohConstants.ZONE_TILE_LENGTH + val baseZoneZ = (region and 0xFF) * PohConstants.ZONE_TILE_LENGTH + return ZoneKey(baseZoneX + room.templateZoneX, baseZoneZ + room.templateZoneZ, style.level) + } +} diff --git a/api/poh/src/main/kotlin/org/rsmod/api/poh/hook/PohLifecycleScript.kt b/api/poh/src/main/kotlin/org/rsmod/api/poh/hook/PohLifecycleScript.kt new file mode 100644 index 000000000..a3c48e920 --- /dev/null +++ b/api/poh/src/main/kotlin/org/rsmod/api/poh/hook/PohLifecycleScript.kt @@ -0,0 +1,25 @@ +package org.rsmod.api.poh.hook + +import jakarta.inject.Inject +import org.rsmod.api.poh.PohDataStore +import org.rsmod.api.poh.PohManager +import org.rsmod.api.script.onPlayerLogin +import org.rsmod.api.script.onPlayerLogout +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Releases house regions on logout and forces [PohDataStore] to load (and verify its manifest) at + * server startup rather than on first house entry. + */ +internal class PohLifecycleScript +@Inject +constructor( + private val manager: PohManager, + @Suppress("unused") private val dataStore: PohDataStore, +) : PluginScript() { + override fun ScriptContext.startup() { + onPlayerLogin { manager.handleLogin(player) } + onPlayerLogout { manager.handleLogout(player) } + } +} diff --git a/api/poh/src/main/resources/poh_hotspots.json b/api/poh/src/main/resources/poh_hotspots.json new file mode 100644 index 000000000..d91d75b2c --- /dev/null +++ b/api/poh/src/main/resources/poh_hotspots.json @@ -0,0 +1,8595 @@ +{ + "hotspots": [ + { + "index": 0, + "hotspotLoc": "loc.poh_parlour_5", + "roomKey": "parlour", + "name": "Bookcase space", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 1, + "hotspotLoc": "loc.poh_parlour_7", + "roomKey": "parlour", + "name": "Curtain space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 2, + "hotspotLoc": "loc.poh_parlour_7", + "roomKey": "parlour", + "name": "Curtain space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 3, + "hotspotLoc": "loc.poh_parlour_7", + "roomKey": "parlour", + "name": "Curtain space", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 4, + "hotspotLoc": "loc.poh_parlour_4_corner", + "roomKey": "parlour", + "name": "Rug space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 5, + "hotspotLoc": "loc.poh_parlour_4_side", + "roomKey": "parlour", + "name": "Rug space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 6, + "hotspotLoc": "loc.poh_parlour_1", + "roomKey": "parlour", + "name": "Chair space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 11, + "rotation": 2 + }, + { + "index": 7, + "hotspotLoc": "loc.poh_parlour_4_side", + "roomKey": "parlour", + "name": "Rug space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 8, + "hotspotLoc": "loc.poh_parlour_4_corner", + "roomKey": "parlour", + "name": "Rug space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 9, + "hotspotLoc": "loc.poh_parlour_7", + "roomKey": "parlour", + "name": "Curtain space", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 10, + "hotspotLoc": "loc.poh_parlour_4_side", + "roomKey": "parlour", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 11, + "hotspotLoc": "loc.poh_parlour_4_middle", + "roomKey": "parlour", + "name": "Rug space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 12, + "hotspotLoc": "loc.poh_parlour_4_middle", + "roomKey": "parlour", + "name": "Rug space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 13, + "hotspotLoc": "loc.poh_parlour_4_side", + "roomKey": "parlour", + "name": "Rug space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 14, + "hotspotLoc": "loc.poh_parlour_6", + "roomKey": "parlour", + "name": "Fireplace space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 15, + "hotspotLoc": "loc.poh_parlour_4_side", + "roomKey": "parlour", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 16, + "hotspotLoc": "loc.poh_parlour_3", + "roomKey": "parlour", + "name": "Chair space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 17, + "hotspotLoc": "loc.poh_parlour_4_middle", + "roomKey": "parlour", + "name": "Rug space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 18, + "hotspotLoc": "loc.poh_parlour_4_middle", + "roomKey": "parlour", + "name": "Rug space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 19, + "hotspotLoc": "loc.poh_parlour_4_side", + "roomKey": "parlour", + "name": "Rug space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 20, + "hotspotLoc": "loc.poh_parlour_7", + "roomKey": "parlour", + "name": "Curtain space", + "localX": 5, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 21, + "hotspotLoc": "loc.poh_parlour_4_corner", + "roomKey": "parlour", + "name": "Rug space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 22, + "hotspotLoc": "loc.poh_parlour_4_side", + "roomKey": "parlour", + "name": "Rug space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 23, + "hotspotLoc": "loc.poh_parlour_2", + "roomKey": "parlour", + "name": "Chair space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 11, + "rotation": 1 + }, + { + "index": 24, + "hotspotLoc": "loc.poh_parlour_4_side", + "roomKey": "parlour", + "name": "Rug space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 25, + "hotspotLoc": "loc.poh_parlour_4_corner", + "roomKey": "parlour", + "name": "Rug space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 26, + "hotspotLoc": "loc.poh_parlour_7", + "roomKey": "parlour", + "name": "Curtain space", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 27, + "hotspotLoc": "loc.poh_parlour_5", + "roomKey": "parlour", + "name": "Bookcase space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 28, + "hotspotLoc": "loc.poh_parlour_7", + "roomKey": "parlour", + "name": "Curtain space", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 29, + "hotspotLoc": "loc.poh_parlour_7", + "roomKey": "parlour", + "name": "Curtain space", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 30, + "hotspotLoc": "loc.poh_kitchen_4", + "roomKey": "kitchen", + "name": "Cat basket space", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 31, + "hotspotLoc": "loc.poh_kitchen_3", + "roomKey": "kitchen", + "name": "Barrel space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 32, + "hotspotLoc": "loc.poh_kitchen_8", + "roomKey": "kitchen", + "name": "Spice Rack", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 33, + "hotspotLoc": "loc.poh_kitchen_2_crockery", + "roomKey": "kitchen", + "name": "Shelf space", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 34, + "hotspotLoc": "loc.poh_kitchen_7", + "roomKey": "kitchen", + "name": "Table space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 35, + "hotspotLoc": "loc.poh_kitchen_1", + "roomKey": "kitchen", + "name": "Stove space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 36, + "hotspotLoc": "loc.poh_kitchen_5", + "roomKey": "kitchen", + "name": "Larder space", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 37, + "hotspotLoc": "loc.poh_kitchen_2_crockery", + "roomKey": "kitchen", + "name": "Shelf space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 38, + "hotspotLoc": "loc.poh_kitchen_6", + "roomKey": "kitchen", + "name": "Sink space", + "localX": 7, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 39, + "hotspotLoc": "loc.poh_kitchen_2", + "roomKey": "kitchen", + "name": "Shelf space", + "localX": 7, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 40, + "hotspotLoc": "loc.poh_dining_room_7", + "roomKey": "dining_room", + "name": "Bell pull space", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 41, + "hotspotLoc": "loc.poh_dining_room_5", + "roomKey": "dining_room", + "name": "Curtain space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 42, + "hotspotLoc": "loc.poh_dining_room_5", + "roomKey": "dining_room", + "name": "Curtain space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 43, + "hotspotLoc": "loc.poh_dining_room_5", + "roomKey": "dining_room", + "name": "Curtain space", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 44, + "hotspotLoc": "loc.poh_dining_room_3", + "roomKey": "dining_room", + "name": "Seating space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 45, + "hotspotLoc": "loc.poh_dining_room_1", + "roomKey": "dining_room", + "name": "Table space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 46, + "hotspotLoc": "loc.poh_dining_room_2", + "roomKey": "dining_room", + "name": "Seating space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 47, + "hotspotLoc": "loc.poh_dining_room_6", + "roomKey": "dining_room", + "name": "Decoration space", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 48, + "hotspotLoc": "loc.poh_dining_room_3", + "roomKey": "dining_room", + "name": "Seating space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 49, + "hotspotLoc": "loc.poh_dining_room_2", + "roomKey": "dining_room", + "name": "Seating space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 50, + "hotspotLoc": "loc.poh_dining_room_4", + "roomKey": "dining_room", + "name": "Fireplace space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 51, + "hotspotLoc": "loc.poh_dining_room_3", + "roomKey": "dining_room", + "name": "Seating space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 52, + "hotspotLoc": "loc.poh_dining_room_2", + "roomKey": "dining_room", + "name": "Seating space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 53, + "hotspotLoc": "loc.poh_dining_room_5", + "roomKey": "dining_room", + "name": "Curtain space", + "localX": 5, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 54, + "hotspotLoc": "loc.poh_dining_room_3", + "roomKey": "dining_room", + "name": "Seating space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 55, + "hotspotLoc": "loc.poh_dining_room_2", + "roomKey": "dining_room", + "name": "Seating space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 56, + "hotspotLoc": "loc.poh_dining_room_6", + "roomKey": "dining_room", + "name": "Decoration space", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 57, + "hotspotLoc": "loc.poh_dining_room_5", + "roomKey": "dining_room", + "name": "Curtain space", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 58, + "hotspotLoc": "loc.poh_dining_room_5", + "roomKey": "dining_room", + "name": "Curtain space", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 59, + "hotspotLoc": "loc.poh_bedroom_4", + "roomKey": "bedroom", + "name": "Curtain space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 60, + "hotspotLoc": "loc.poh_bedroom_4", + "roomKey": "bedroom", + "name": "Curtain space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 61, + "hotspotLoc": "loc.poh_bedroom_3", + "roomKey": "bedroom", + "name": "Dresser space", + "localX": 0, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 62, + "hotspotLoc": "loc.poh_bedroom_5_corner", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 63, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 64, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 65, + "hotspotLoc": "loc.poh_bedroom_5_corner", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 66, + "hotspotLoc": "loc.poh_bedroom_4", + "roomKey": "bedroom", + "name": "Curtain space", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 67, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 68, + "hotspotLoc": "loc.poh_bedroom_5_middle", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 69, + "hotspotLoc": "loc.poh_bedroom_5_middle", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 70, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 71, + "hotspotLoc": "loc.poh_bedroom_4", + "roomKey": "bedroom", + "name": "Curtain space", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 72, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 73, + "hotspotLoc": "loc.poh_bedroom_5_middle", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 74, + "hotspotLoc": "loc.poh_bedroom_5_middle", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 75, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 76, + "hotspotLoc": "loc.poh_bedroom_1_doublebed", + "roomKey": "bedroom", + "name": "Bed space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 77, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 78, + "hotspotLoc": "loc.poh_bedroom_5_middle", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 79, + "hotspotLoc": "loc.poh_bedroom_5_middle", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 80, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 81, + "hotspotLoc": "loc.poh_bedroom_4", + "roomKey": "bedroom", + "name": "Curtain space", + "localX": 5, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 82, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 83, + "hotspotLoc": "loc.poh_bedroom_5_middle", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 84, + "hotspotLoc": "loc.poh_bedroom_5_middle", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 85, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 86, + "hotspotLoc": "loc.poh_bedroom_4", + "roomKey": "bedroom", + "name": "Curtain space", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 87, + "hotspotLoc": "loc.poh_bedroom_5_corner", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 88, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 89, + "hotspotLoc": "loc.poh_bedroom_5_side", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 90, + "hotspotLoc": "loc.poh_bedroom_5_corner", + "roomKey": "bedroom", + "name": "Rug space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 91, + "hotspotLoc": "loc.poh_bedroom_2", + "roomKey": "bedroom", + "name": "Wardrobe space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 92, + "hotspotLoc": "loc.poh_bedroom_7", + "roomKey": "bedroom", + "name": "Corner space", + "localX": 7, + "localZ": 0, + "level": 0, + "shape": 11, + "rotation": 1 + }, + { + "index": 93, + "hotspotLoc": "loc.poh_bedroom_4", + "roomKey": "bedroom", + "name": "Curtain space", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 94, + "hotspotLoc": "loc.poh_bedroom_6", + "roomKey": "bedroom", + "name": "Fireplace space", + "localX": 7, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 95, + "hotspotLoc": "loc.poh_bedroom_4", + "roomKey": "bedroom", + "name": "Curtain space", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 96, + "hotspotLoc": "loc.poh_hall1_7", + "roomKey": "skill_hall_stairs_up", + "name": "Rune case space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 97, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 98, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 99, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 100, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 101, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 102, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 103, + "hotspotLoc": "loc.poh_hall1_4", + "roomKey": "skill_hall_stairs_up", + "name": "Fishing trophy space", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 104, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 105, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 106, + "hotspotLoc": "loc.poh_hall1_6", + "roomKey": "skill_hall_stairs_up", + "name": "Armour space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 107, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 108, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 109, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 110, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 111, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 112, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 113, + "hotspotLoc": "loc.poh_hall1_1_stairs_up", + "roomKey": "skill_hall_stairs_up", + "name": "Stair Space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 114, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 115, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 116, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 117, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 118, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 119, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 120, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 121, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 122, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 123, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 124, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 125, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 126, + "hotspotLoc": "loc.poh_hall1_5", + "roomKey": "skill_hall_stairs_up", + "name": "Armour space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 127, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 128, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 129, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 130, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 131, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 132, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 133, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 134, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 135, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 136, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "skill_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 137, + "hotspotLoc": "loc.poh_hall1_2", + "roomKey": "skill_hall_stairs_up", + "name": "Head trophy space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 138, + "hotspotLoc": "loc.poh_hall1_7", + "roomKey": "skill_hall_stairs_top", + "name": "Rune case space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 139, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 140, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 141, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 142, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 143, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 144, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 145, + "hotspotLoc": "loc.poh_hall1_4", + "roomKey": "skill_hall_stairs_top", + "name": "Fishing trophy space", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 146, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 147, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 148, + "hotspotLoc": "loc.poh_hall1_6", + "roomKey": "skill_hall_stairs_top", + "name": "Armour space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 149, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 150, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 151, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 152, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 153, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 154, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 155, + "hotspotLoc": "loc.poh_hall1_1_stairs_top", + "roomKey": "skill_hall_stairs_top", + "name": "Stair Space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 156, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 157, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 158, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 159, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 160, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 161, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 162, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 163, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 164, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 165, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 166, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 167, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 168, + "hotspotLoc": "loc.poh_hall1_5", + "roomKey": "skill_hall_stairs_top", + "name": "Armour space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 169, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 170, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 171, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 172, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 173, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 174, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 175, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 176, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 177, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 178, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "skill_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 179, + "hotspotLoc": "loc.poh_hall1_2", + "roomKey": "skill_hall_stairs_top", + "name": "Head trophy space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 180, + "hotspotLoc": "loc.poh_hall2_7", + "roomKey": "quest_hall_stairs_up", + "name": "Bookcase space", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 181, + "hotspotLoc": "loc.poh_hall2_4", + "roomKey": "quest_hall_stairs_up", + "name": "Guild trophy space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 182, + "hotspotLoc": "loc.poh_hall2_1_corner", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 183, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 184, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 185, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 186, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 187, + "hotspotLoc": "loc.poh_hall2_1_corner", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 188, + "hotspotLoc": "loc.poh_hall2_2", + "roomKey": "quest_hall_stairs_up", + "name": "Portrait space", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 189, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 190, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 191, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 192, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 193, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 194, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 195, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 196, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 197, + "hotspotLoc": "loc.poh_hall2_1_stairs_up", + "roomKey": "quest_hall_stairs_up", + "name": "Stair Space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 198, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 199, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 200, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 201, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 202, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 203, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 204, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 205, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 206, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 207, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 208, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 209, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 210, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 211, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 212, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 213, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 214, + "hotspotLoc": "loc.poh_hall2_1_corner", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 215, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 216, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 217, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 218, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 219, + "hotspotLoc": "loc.poh_hall2_1_corner", + "roomKey": "quest_hall_stairs_up", + "name": "Rug space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 220, + "hotspotLoc": "loc.poh_hall2_3", + "roomKey": "quest_hall_stairs_up", + "name": "Landscape space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 221, + "hotspotLoc": "loc.poh_hall2_6", + "roomKey": "quest_hall_stairs_up", + "name": "Map space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 222, + "hotspotLoc": "loc.poh_hall2_5", + "roomKey": "quest_hall_stairs_up", + "name": "Sword space", + "localX": 7, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 223, + "hotspotLoc": "loc.poh_hall2_7", + "roomKey": "quest_hall_stairs_top", + "name": "Bookcase space", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 224, + "hotspotLoc": "loc.poh_hall2_4", + "roomKey": "quest_hall_stairs_top", + "name": "Guild trophy space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 225, + "hotspotLoc": "loc.poh_hall2_1_corner", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 226, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 227, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 228, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 229, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 230, + "hotspotLoc": "loc.poh_hall2_1_corner", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 231, + "hotspotLoc": "loc.poh_hall2_2", + "roomKey": "quest_hall_stairs_top", + "name": "Portrait space", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 232, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 233, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 234, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 235, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 236, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 237, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 238, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 239, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 240, + "hotspotLoc": "loc.poh_hall2_1_stairs_top", + "roomKey": "quest_hall_stairs_top", + "name": "Stair Space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 241, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 242, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 243, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 244, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 245, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 246, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 247, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 248, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 249, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 250, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 251, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 252, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 253, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 254, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 255, + "hotspotLoc": "loc.poh_hall2_1_middle", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 256, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 257, + "hotspotLoc": "loc.poh_hall2_1_corner", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 258, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 259, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 260, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 261, + "hotspotLoc": "loc.poh_hall2_1_side", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 262, + "hotspotLoc": "loc.poh_hall2_1_corner", + "roomKey": "quest_hall_stairs_top", + "name": "Rug space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 263, + "hotspotLoc": "loc.poh_hall2_3", + "roomKey": "quest_hall_stairs_top", + "name": "Landscape space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 264, + "hotspotLoc": "loc.poh_hall2_6", + "roomKey": "quest_hall_stairs_top", + "name": "Map space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 265, + "hotspotLoc": "loc.poh_hall2_5", + "roomKey": "quest_hall_stairs_top", + "name": "Sword space", + "localX": 7, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 266, + "hotspotLoc": "loc.poh_workshop_3c", + "roomKey": "workshop", + "name": "Tool space", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 267, + "hotspotLoc": "loc.poh_workshop_2", + "roomKey": "workshop", + "name": "Clockmaking space", + "localX": 0, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 268, + "hotspotLoc": "loc.poh_workshop_3e", + "roomKey": "workshop", + "name": "Tool space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 269, + "hotspotLoc": "loc.poh_workshop_3a", + "roomKey": "workshop", + "name": "Tool space", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 270, + "hotspotLoc": "loc.poh_workshop_2_stool", + "roomKey": "workshop", + "name": "Nothing", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 271, + "hotspotLoc": "loc.poh_workshop_1_stool", + "roomKey": "workshop", + "name": "Nothing", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 272, + "hotspotLoc": "loc.poh_workshop_1", + "roomKey": "workshop", + "name": "Workbench space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 273, + "hotspotLoc": "loc.poh_workshop_3b", + "roomKey": "workshop", + "name": "Tool space", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 274, + "hotspotLoc": "loc.poh_workshop_3d", + "roomKey": "workshop", + "name": "Tool space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 275, + "hotspotLoc": "loc.poh_workshop_4", + "roomKey": "workshop", + "name": "Repair space", + "localX": 7, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 276, + "hotspotLoc": "loc.poh_workshop_5", + "roomKey": "workshop", + "name": "Heraldry space", + "localX": 7, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 277, + "hotspotLoc": "loc.poh_chapel_6", + "roomKey": "chapel", + "name": "Statue space", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 11, + "rotation": 2 + }, + { + "index": 278, + "hotspotLoc": "loc.poh_chapelwindow_hotspot_rimmington", + "roomKey": "chapel", + "name": "Window space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 279, + "hotspotLoc": "loc.poh_chapelwindow_hotspot_rimmington", + "roomKey": "chapel", + "name": "Window space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 280, + "hotspotLoc": "loc.poh_chapel_3", + "roomKey": "chapel", + "name": "Lamp space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 281, + "hotspotLoc": "loc.poh_chapelwindow_hotspot_rimmington", + "roomKey": "chapel", + "name": "Window space", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 282, + "hotspotLoc": "loc.poh_chapel_5_corner", + "roomKey": "chapel", + "name": "Rug space", + "localX": 3, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 283, + "hotspotLoc": "loc.poh_chapel_5_side", + "roomKey": "chapel", + "name": "Rug space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 284, + "hotspotLoc": "loc.poh_chapel_5_side", + "roomKey": "chapel", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 285, + "hotspotLoc": "loc.poh_chapel_5_side", + "roomKey": "chapel", + "name": "Rug space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 286, + "hotspotLoc": "loc.poh_chapel_5_corner", + "roomKey": "chapel", + "name": "Rug space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 287, + "hotspotLoc": "loc.poh_chapel_2", + "roomKey": "chapel", + "name": "Altar space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 288, + "hotspotLoc": "loc.poh_chapel_1", + "roomKey": "chapel", + "name": "Icon space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 289, + "hotspotLoc": "loc.poh_chapel_5_corner", + "roomKey": "chapel", + "name": "Rug space", + "localX": 4, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 290, + "hotspotLoc": "loc.poh_chapel_5_side", + "roomKey": "chapel", + "name": "Rug space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 291, + "hotspotLoc": "loc.poh_chapel_5_side", + "roomKey": "chapel", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 292, + "hotspotLoc": "loc.poh_chapel_5_side", + "roomKey": "chapel", + "name": "Rug space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 293, + "hotspotLoc": "loc.poh_chapel_5_corner", + "roomKey": "chapel", + "name": "Rug space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 294, + "hotspotLoc": "loc.poh_chapelwindow_hotspot_rimmington", + "roomKey": "chapel", + "name": "Window space", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 295, + "hotspotLoc": "loc.poh_chapel_3", + "roomKey": "chapel", + "name": "Lamp space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 296, + "hotspotLoc": "loc.poh_chapel_6", + "roomKey": "chapel", + "name": "Statue space", + "localX": 7, + "localZ": 0, + "level": 0, + "shape": 11, + "rotation": 1 + }, + { + "index": 297, + "hotspotLoc": "loc.poh_chapelwindow_hotspot_rimmington", + "roomKey": "chapel", + "name": "Window space", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 298, + "hotspotLoc": "loc.poh_chapel_7", + "roomKey": "chapel", + "name": "Musical space", + "localX": 7, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 299, + "hotspotLoc": "loc.poh_chapelwindow_hotspot_rimmington", + "roomKey": "chapel", + "name": "Window space", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 300, + "hotspotLoc": "loc.poh_study_5", + "roomKey": "study", + "name": "Wall chart space", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 301, + "hotspotLoc": "loc.poh_study_2", + "roomKey": "study", + "name": "Globe space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 302, + "hotspotLoc": "loc.poh_study_5", + "roomKey": "study", + "name": "Wall chart space", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 303, + "hotspotLoc": "loc.poh_study_1", + "roomKey": "study", + "name": "Lectern space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 304, + "hotspotLoc": "loc.poh_study_7", + "roomKey": "study", + "name": "Bookcase space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 305, + "hotspotLoc": "loc.poh_study_7", + "roomKey": "study", + "name": "Bookcase space", + "localX": 4, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 306, + "hotspotLoc": "loc.poh_study_4", + "roomKey": "study", + "name": "Crystal ball space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 307, + "hotspotLoc": "loc.poh_study_6", + "roomKey": "study", + "name": "Telescope space", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 308, + "hotspotLoc": "loc.poh_study_5", + "roomKey": "study", + "name": "Wall chart space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 309, + "hotspotLoc": "loc.poh_study_5", + "roomKey": "study", + "name": "Wall chart space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 310, + "hotspotLoc": "loc.poh_throne_room_5", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 311, + "hotspotLoc": "loc.poh_throne_room_5", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 312, + "hotspotLoc": "loc.poh_throne_room_5", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 313, + "hotspotLoc": "loc.poh_throne_room_5", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 0, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 314, + "hotspotLoc": "loc.poh_throne_room_5", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 0, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 315, + "hotspotLoc": "loc.poh_throne_room_5", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 316, + "hotspotLoc": "loc.poh_throne_room_7", + "roomKey": "throne_room", + "name": "Trapdoor space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 317, + "hotspotLoc": "loc.poh_throne_room_3_rimmington", + "roomKey": "throne_room", + "name": "Floor space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 318, + "hotspotLoc": "loc.poh_throne_room_3_rimmington", + "roomKey": "throne_room", + "name": "Floor space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 319, + "hotspotLoc": "loc.poh_throne_room_1", + "roomKey": "throne_room", + "name": "Throne space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 320, + "hotspotLoc": "loc.poh_throne_room_3_q", + "roomKey": "throne_room", + "name": "Decoration space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 321, + "hotspotLoc": "loc.poh_throne_room_3_rimmington", + "roomKey": "throne_room", + "name": "Floor space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 322, + "hotspotLoc": "loc.poh_throne_room_3_rimmington", + "roomKey": "throne_room", + "name": "Floor space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 323, + "hotspotLoc": "loc.poh_throne_room_1", + "roomKey": "throne_room", + "name": "Throne space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 324, + "hotspotLoc": "loc.poh_throne_room_3_q", + "roomKey": "throne_room", + "name": "Decoration space", + "localX": 4, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 325, + "hotspotLoc": "loc.poh_throne_room_4", + "roomKey": "throne_room", + "name": "Lever space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 326, + "hotspotLoc": "loc.poh_throne_room_6", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 7, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 327, + "hotspotLoc": "loc.poh_throne_room_6", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 328, + "hotspotLoc": "loc.poh_throne_room_6", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 329, + "hotspotLoc": "loc.poh_throne_room_6", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 7, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 330, + "hotspotLoc": "loc.poh_throne_room_6", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 7, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 331, + "hotspotLoc": "loc.poh_throne_room_6", + "roomKey": "throne_room", + "name": "Seating space", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 332, + "hotspotLoc": "loc.poh_teleroom_1", + "roomKey": "portal_chamber", + "name": "Portal space", + "localX": 0, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 333, + "hotspotLoc": "loc.poh_teleroom_7", + "roomKey": "portal_chamber", + "name": "Centrepiece space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 334, + "hotspotLoc": "loc.poh_teleroom_2", + "roomKey": "portal_chamber", + "name": "Portal space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 335, + "hotspotLoc": "loc.poh_teleroom_3", + "roomKey": "portal_chamber", + "name": "Portal space", + "localX": 7, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 336, + "hotspotLoc": "loc.poh_combat_room_6", + "roomKey": "combat_room", + "name": "Combat dummy space", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 337, + "hotspotLoc": "loc.poh_gr_1_wall_cobat_corner", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 3, + "rotation": 1 + }, + { + "index": 338, + "hotspotLoc": "loc.poh_gr_1_wall_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 339, + "hotspotLoc": "loc.poh_gr_1_wall_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 340, + "hotspotLoc": "loc.poh_gr_1_wall_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 341, + "hotspotLoc": "loc.poh_gr_1_wall_everything", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 342, + "hotspotLoc": "loc.poh_gr_1_wall_redcorner", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 3, + "rotation": 2 + }, + { + "index": 343, + "hotspotLoc": "loc.poh_combat_room_5", + "roomKey": "combat_room", + "name": "Decoration space", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 344, + "hotspotLoc": "loc.poh_gr_1_wall_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 345, + "hotspotLoc": "loc.poh_gr_1_floor_sw", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 346, + "hotspotLoc": "loc.poh_gr_1_floor_side", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 347, + "hotspotLoc": "loc.poh_gr_1_wall_ranging_agility", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 348, + "hotspotLoc": "loc.poh_gr_1_floor_side", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 349, + "hotspotLoc": "loc.poh_gr_1_floor_nw", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 350, + "hotspotLoc": "loc.poh_gr_1_wall_everything", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 351, + "hotspotLoc": "loc.poh_gr_1_wall_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 352, + "hotspotLoc": "loc.poh_gr_1_floor_s", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 353, + "hotspotLoc": "loc.poh_gr_1_floor_middle", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 354, + "hotspotLoc": "loc.poh_gr_1_wall_agility", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 355, + "hotspotLoc": "loc.poh_gr_1_floor_middle", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 356, + "hotspotLoc": "loc.poh_gr_1_wall_ranging", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 357, + "hotspotLoc": "loc.poh_gr_1_floor_n", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 358, + "hotspotLoc": "loc.poh_gr_1_wall_combat_agility", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 359, + "hotspotLoc": "loc.poh_combat_room_4", + "roomKey": "combat_room", + "name": "Storage space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 360, + "hotspotLoc": "loc.poh_gr_1_wall_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 361, + "hotspotLoc": "loc.poh_gr_1_wall_ranging", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 362, + "hotspotLoc": "loc.poh_gr_1_floor_s", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 363, + "hotspotLoc": "loc.poh_gr_1_floor_middle", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 364, + "hotspotLoc": "loc.poh_gr_1_wall_agility", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 365, + "hotspotLoc": "loc.poh_gr_1_floor_middle", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 366, + "hotspotLoc": "loc.poh_gr_1_floor_n", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 367, + "hotspotLoc": "loc.poh_gr_1_wall_combat_agility", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 368, + "hotspotLoc": "loc.poh_gr_1_wall_ranging_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 369, + "hotspotLoc": "loc.poh_gr_1_floor_se", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 370, + "hotspotLoc": "loc.poh_gr_1_wall_ranging", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 371, + "hotspotLoc": "loc.poh_gr_1_floor_side", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 372, + "hotspotLoc": "loc.poh_gr_1_wall_agility", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 373, + "hotspotLoc": "loc.poh_gr_1_floor_side", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 374, + "hotspotLoc": "loc.poh_gr_1_floor_ne", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 375, + "hotspotLoc": "loc.poh_gr_1_wall_combat_agility", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 376, + "hotspotLoc": "loc.poh_gr_1_wall_bluecorner", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 3, + "rotation": 0 + }, + { + "index": 377, + "hotspotLoc": "loc.poh_gr_1_wall_ranging_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 378, + "hotspotLoc": "loc.poh_gr_1_wall_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 379, + "hotspotLoc": "loc.poh_gr_1_wall_combat", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 380, + "hotspotLoc": "loc.poh_gr_1_wall_combat_agility", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 381, + "hotspotLoc": "loc.poh_gr_1_wall_combat_agility_corner", + "roomKey": "combat_room", + "name": "Combat ring space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 3, + "rotation": 3 + }, + { + "index": 382, + "hotspotLoc": "loc.poh_combat_room_5", + "roomKey": "combat_room", + "name": "Decoration space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 383, + "hotspotLoc": "loc.poh_games_room_7", + "roomKey": "games_room", + "name": "Ranging game space", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 384, + "hotspotLoc": "loc.poh_games_room_5", + "roomKey": "games_room", + "name": "Stone space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 385, + "hotspotLoc": "loc.poh_games_room_4", + "roomKey": "games_room", + "name": "Prize chest space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 386, + "hotspotLoc": "loc.poh_games_room_6", + "roomKey": "games_room", + "name": "Elemental balance space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 387, + "hotspotLoc": "loc.poh_games_room_2", + "roomKey": "games_room", + "name": "Game space", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 388, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "treasure_room", + "name": "Decoration space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 389, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "treasure_room", + "name": "Lighting space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 390, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "treasure_room", + "name": "Lighting space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 391, + "hotspotLoc": "loc.poh_dungeon_treasure_1", + "roomKey": "treasure_room", + "name": "Treasure space", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 392, + "hotspotLoc": "loc.poh_dungeon_4r", + "roomKey": "treasure_room", + "name": "Door space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 393, + "hotspotLoc": "loc.poh_dungeon_treasure_2", + "roomKey": "treasure_room", + "name": "Monster space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 394, + "hotspotLoc": "loc.poh_dungeon_treasure_5", + "roomKey": "treasure_room", + "name": "Decoration space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 395, + "hotspotLoc": "loc.poh_dungeon_4l", + "roomKey": "treasure_room", + "name": "Door space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 396, + "hotspotLoc": "loc.poh_dungeon_treasure_5", + "roomKey": "treasure_room", + "name": "Decoration space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 397, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "treasure_room", + "name": "Lighting space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 398, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "treasure_room", + "name": "Decoration space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 399, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "treasure_room", + "name": "Lighting space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 400, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "dungeon_corridor", + "name": "Decoration space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 4, + "rotation": 3 + }, + { + "index": 401, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_corridor", + "name": "Lighting space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 4, + "rotation": 1 + }, + { + "index": 402, + "hotspotLoc": "loc.poh_dungeon_5r", + "roomKey": "dungeon_corridor", + "name": "Door space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 403, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_corridor", + "name": "Lighting space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 4, + "rotation": 0 + }, + { + "index": 404, + "hotspotLoc": "loc.poh_dungeon_3", + "roomKey": "dungeon_corridor", + "name": "Trap space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 405, + "hotspotLoc": "loc.poh_dungeon_1", + "roomKey": "dungeon_corridor", + "name": "Guard space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 406, + "hotspotLoc": "loc.poh_dungeon_2", + "roomKey": "dungeon_corridor", + "name": "Trap space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 407, + "hotspotLoc": "loc.poh_dungeon_4l", + "roomKey": "dungeon_corridor", + "name": "Door space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 408, + "hotspotLoc": "loc.poh_dungeon_5l", + "roomKey": "dungeon_corridor", + "name": "Door space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 409, + "hotspotLoc": "loc.poh_dungeon_3", + "roomKey": "dungeon_corridor", + "name": "Trap space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 410, + "hotspotLoc": "loc.poh_dungeon_2", + "roomKey": "dungeon_corridor", + "name": "Trap space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 411, + "hotspotLoc": "loc.poh_dungeon_4r", + "roomKey": "dungeon_corridor", + "name": "Door space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 412, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_corridor", + "name": "Lighting space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 4, + "rotation": 2 + }, + { + "index": 413, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_corridor", + "name": "Lighting space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 4, + "rotation": 3 + }, + { + "index": 414, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "dungeon_corridor", + "name": "Decoration space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 4, + "rotation": 1 + }, + { + "index": 415, + "hotspotLoc": "loc.poh_dungeon_stairs_3", + "roomKey": "dungeon_stairs", + "name": "Guard space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 416, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_stairs", + "name": "Lighting space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 4, + "rotation": 0 + }, + { + "index": 417, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_stairs", + "name": "Lighting space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 4, + "rotation": 0 + }, + { + "index": 418, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "dungeon_stairs", + "name": "Decoration space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 419, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_stairs", + "name": "Lighting space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 420, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 421, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 422, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 423, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 424, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_stairs", + "name": "Lighting space", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 425, + "hotspotLoc": "loc.poh_dungeon_5r", + "roomKey": "dungeon_stairs", + "name": "Door space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 426, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 427, + "hotspotLoc": "loc.poh_hall1_1_stairs_up", + "roomKey": "dungeon_stairs", + "name": "Stair Space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 428, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 429, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 430, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 431, + "hotspotLoc": "loc.poh_dungeon_4l", + "roomKey": "dungeon_stairs", + "name": "Door space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 432, + "hotspotLoc": "loc.poh_dungeon_5l", + "roomKey": "dungeon_stairs", + "name": "Door space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 433, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 434, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 435, + "hotspotLoc": "loc.poh_hall1_1_middle", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 436, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 437, + "hotspotLoc": "loc.poh_dungeon_4r", + "roomKey": "dungeon_stairs", + "name": "Door space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 438, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_stairs", + "name": "Lighting space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 439, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 440, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 441, + "hotspotLoc": "loc.poh_hall1_1_side", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 442, + "hotspotLoc": "loc.poh_dungeon_stairs_2", + "roomKey": "dungeon_stairs", + "name": "Guard space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 443, + "hotspotLoc": "loc.poh_hall1_1_corner", + "roomKey": "dungeon_stairs", + "name": "Rug space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 444, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_stairs", + "name": "Lighting space", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 445, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "dungeon_stairs", + "name": "Decoration space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 446, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_stairs", + "name": "Lighting space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 4, + "rotation": 2 + }, + { + "index": 447, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_stairs", + "name": "Lighting space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 4, + "rotation": 2 + }, + { + "index": 448, + "hotspotLoc": "loc.poh_dungeon_5r", + "roomKey": "dungeon_junction", + "name": "Door space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 449, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_junction", + "name": "Lighting space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 4, + "rotation": 0 + }, + { + "index": 450, + "hotspotLoc": "loc.poh_dungeon_3", + "roomKey": "dungeon_junction", + "name": "Trap space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 451, + "hotspotLoc": "loc.poh_dungeon_1", + "roomKey": "dungeon_junction", + "name": "Guard space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 452, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "dungeon_junction", + "name": "Decoration space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 4, + "rotation": 0 + }, + { + "index": 453, + "hotspotLoc": "loc.poh_dungeon_2", + "roomKey": "dungeon_junction", + "name": "Trap space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 454, + "hotspotLoc": "loc.poh_dungeon_4l", + "roomKey": "dungeon_junction", + "name": "Door space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 455, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_junction", + "name": "Lighting space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 4, + "rotation": 0 + }, + { + "index": 456, + "hotspotLoc": "loc.poh_dungeon_5l", + "roomKey": "dungeon_junction", + "name": "Door space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 457, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_junction", + "name": "Lighting space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 4, + "rotation": 2 + }, + { + "index": 458, + "hotspotLoc": "loc.poh_dungeon_3", + "roomKey": "dungeon_junction", + "name": "Trap space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 459, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "dungeon_junction", + "name": "Decoration space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 4, + "rotation": 2 + }, + { + "index": 460, + "hotspotLoc": "loc.poh_dungeon_2", + "roomKey": "dungeon_junction", + "name": "Trap space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 461, + "hotspotLoc": "loc.poh_dungeon_4r", + "roomKey": "dungeon_junction", + "name": "Door space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 462, + "hotspotLoc": "loc.poh_dungeon_6", + "roomKey": "dungeon_junction", + "name": "Lighting space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 4, + "rotation": 2 + }, + { + "index": 463, + "hotspotLoc": "loc.poh_oubliette_3", + "roomKey": "oubliette", + "name": "Guard space", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 464, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "oubliette", + "name": "Decoration space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 465, + "hotspotLoc": "loc.poh_oubliette_4", + "roomKey": "oubliette", + "name": "Lighting space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 466, + "hotspotLoc": "loc.poh_oubliette_5", + "roomKey": "oubliette", + "name": "Ladder space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 467, + "hotspotLoc": "loc.poh_oubliette_4", + "roomKey": "oubliette", + "name": "Lighting space", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 468, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 2, + "rotation": 3 + }, + { + "index": 469, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 470, + "hotspotLoc": "loc.poh_oubliette_1_corner", + "roomKey": "oubliette", + "name": "Floor space corner", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 471, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 472, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 473, + "hotspotLoc": "loc.poh_oubliette_1_side", + "roomKey": "oubliette", + "name": "Floor space side", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 474, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 475, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 476, + "hotspotLoc": "loc.poh_oubliette_1_side", + "roomKey": "oubliette", + "name": "Floor space side", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 477, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 2, + "rotation": 0 + }, + { + "index": 478, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 479, + "hotspotLoc": "loc.poh_oubliette_1_corner", + "roomKey": "oubliette", + "name": "Floor space corner", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 480, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "oubliette", + "name": "Decoration space", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 481, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 482, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 483, + "hotspotLoc": "loc.poh_oubliette_1_side", + "roomKey": "oubliette", + "name": "Floor space side", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 484, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 485, + "hotspotLoc": "loc.poh_oubliette_1", + "roomKey": "oubliette", + "name": "Floor space mid", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 486, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 487, + "hotspotLoc": "loc.poh_oubliette_1", + "roomKey": "oubliette", + "name": "Floor space mid", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 488, + "hotspotLoc": "loc.poh_oubliette_2_door", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 489, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 490, + "hotspotLoc": "loc.poh_oubliette_1_side", + "roomKey": "oubliette", + "name": "Floor space side", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 491, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 492, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 493, + "hotspotLoc": "loc.poh_oubliette_1_side", + "roomKey": "oubliette", + "name": "Floor space side", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 494, + "hotspotLoc": "loc.poh_oubliette_1_type8_ogre", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 495, + "hotspotLoc": "loc.poh_oubliette_1", + "roomKey": "oubliette", + "name": "Floor space mid", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 496, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 497, + "hotspotLoc": "loc.poh_oubliette_1", + "roomKey": "oubliette", + "name": "Floor space mid", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 498, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 499, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 500, + "hotspotLoc": "loc.poh_oubliette_1_side", + "roomKey": "oubliette", + "name": "Floor space side", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 501, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "oubliette", + "name": "Decoration space", + "localX": 5, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 502, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 2, + "rotation": 2 + }, + { + "index": 503, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 504, + "hotspotLoc": "loc.poh_oubliette_1_corner", + "roomKey": "oubliette", + "name": "Floor space corner", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 505, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 506, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 507, + "hotspotLoc": "loc.poh_oubliette_1_side", + "roomKey": "oubliette", + "name": "Floor space side", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 508, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 509, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 510, + "hotspotLoc": "loc.poh_oubliette_1_side", + "roomKey": "oubliette", + "name": "Floor space side", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 511, + "hotspotLoc": "loc.poh_oubliette_2_front", + "roomKey": "oubliette", + "name": "Prison space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 2, + "rotation": 1 + }, + { + "index": 512, + "hotspotLoc": "loc.poh_oubliette_1_type8", + "roomKey": "oubliette", + "name": "Floor space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 513, + "hotspotLoc": "loc.poh_oubliette_1_corner", + "roomKey": "oubliette", + "name": "Floor space corner", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 514, + "hotspotLoc": "loc.poh_oubliette_4", + "roomKey": "oubliette", + "name": "Lighting space", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 515, + "hotspotLoc": "loc.poh_oubliette_4", + "roomKey": "oubliette", + "name": "Lighting space", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 516, + "hotspotLoc": "loc.poh_dungeon_7", + "roomKey": "oubliette", + "name": "Decoration space", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 517, + "hotspotLoc": "loc.poh_menagerie_petfeeder_hotspot", + "roomKey": "menagerie_indoor", + "name": "Pet feeder space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 518, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 3, + "rotation": 1 + }, + { + "index": 519, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 520, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 521, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 3, + "rotation": 2 + }, + { + "index": 522, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 523, + "hotspotLoc": "loc.poh_menagerie_combatring_mat_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 524, + "hotspotLoc": "loc.poh_menagerie_combatring_mat_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 525, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 526, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 3, + "rotation": 0 + }, + { + "index": 527, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 528, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 529, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_indoor", + "name": "Arena space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 3, + "rotation": 3 + }, + { + "index": 530, + "hotspotLoc": "loc.poh_menagerie_pethouse_hotspot", + "roomKey": "menagerie_indoor", + "name": "Pet house space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 531, + "hotspotLoc": "loc.poh_menagerie_scratchingpost_hotspot", + "roomKey": "menagerie_indoor", + "name": "Scratching post space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 532, + "hotspotLoc": "loc.poh_menagerie_petlist_hotspot", + "roomKey": "menagerie_indoor", + "name": "Pet list space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 533, + "hotspotLoc": "loc.poh_crude_garden_5", + "roomKey": "garden", + "name": "Big Plant space 2", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 534, + "hotspotLoc": "loc.poh_crude_garden_2", + "roomKey": "garden", + "name": "Big Tree space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 535, + "hotspotLoc": "loc.poh_crude_garden_6", + "roomKey": "garden", + "name": "Small Plant space 1", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 536, + "hotspotLoc": "loc.poh_crude_garden_1", + "roomKey": "garden", + "name": "Centrepiece space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 537, + "hotspotLoc": "loc.poh_crude_garden_7", + "roomKey": "garden", + "name": "Small Plant space 2", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 538, + "hotspotLoc": "loc.poh_crude_garden_4", + "roomKey": "garden", + "name": "Big Plant space 1", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 539, + "hotspotLoc": "loc.poh_garden_8", + "roomKey": "garden", + "name": "Tip jar space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 540, + "hotspotLoc": "loc.poh_crude_garden_3", + "roomKey": "garden", + "name": "Tree space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 541, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 2, + "rotation": 3 + }, + { + "index": 542, + "hotspotLoc": "loc.poh_posh_garden_5cor", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 543, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 544, + "hotspotLoc": "loc.poh_posh_garden_5mid", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 545, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 546, + "hotspotLoc": "loc.poh_posh_garden_5end", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 547, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 548, + "hotspotLoc": "loc.poh_posh_garden_5end", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 549, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 550, + "hotspotLoc": "loc.poh_posh_garden_5mid", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 551, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 0, + "localZ": 7, + "level": 0, + "shape": 2, + "rotation": 0 + }, + { + "index": 552, + "hotspotLoc": "loc.poh_posh_garden_5cor", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 0, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 553, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 554, + "hotspotLoc": "loc.poh_posh_garden_5mid", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 555, + "hotspotLoc": "loc.poh_posh_garden_3", + "roomKey": "formal_garden", + "name": "Big plant 2", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 556, + "hotspotLoc": "loc.poh_posh_garden_7", + "roomKey": "formal_garden", + "name": "Small plant 2", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 557, + "hotspotLoc": "loc.poh_posh_garden_6", + "roomKey": "formal_garden", + "name": "Small plant", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 558, + "hotspotLoc": "loc.poh_posh_garden_2", + "roomKey": "formal_garden", + "name": "Big plant", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 559, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 560, + "hotspotLoc": "loc.poh_posh_garden_5mid", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 561, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 562, + "hotspotLoc": "loc.poh_posh_garden_5end", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 563, + "hotspotLoc": "loc.poh_posh_garden_7", + "roomKey": "formal_garden", + "name": "Small plant 2", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 564, + "hotspotLoc": "loc.poh_posh_garden_6", + "roomKey": "formal_garden", + "name": "Small plant", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 565, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 566, + "hotspotLoc": "loc.poh_posh_garden_5end", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 567, + "hotspotLoc": "loc.poh_posh_garden_1", + "roomKey": "formal_garden", + "name": "Centrepiece space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 568, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 5, + "localZ": 0, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 569, + "hotspotLoc": "loc.poh_posh_garden_5end", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 5, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 570, + "hotspotLoc": "loc.poh_posh_garden_6", + "roomKey": "formal_garden", + "name": "Small plant", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 571, + "hotspotLoc": "loc.poh_posh_garden_7", + "roomKey": "formal_garden", + "name": "Small plant 2", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 572, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 573, + "hotspotLoc": "loc.poh_posh_garden_5end", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 574, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 575, + "hotspotLoc": "loc.poh_posh_garden_5mid", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 576, + "hotspotLoc": "loc.poh_posh_garden_2", + "roomKey": "formal_garden", + "name": "Big plant", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 577, + "hotspotLoc": "loc.poh_posh_garden_6", + "roomKey": "formal_garden", + "name": "Small plant", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 578, + "hotspotLoc": "loc.poh_garden_8", + "roomKey": "formal_garden", + "name": "Tip jar space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 579, + "hotspotLoc": "loc.poh_posh_garden_7", + "roomKey": "formal_garden", + "name": "Small plant 2", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 580, + "hotspotLoc": "loc.poh_posh_garden_3", + "roomKey": "formal_garden", + "name": "Big plant 2", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 581, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 582, + "hotspotLoc": "loc.poh_posh_garden_5mid", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 583, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 7, + "localZ": 0, + "level": 0, + "shape": 2, + "rotation": 2 + }, + { + "index": 584, + "hotspotLoc": "loc.poh_posh_garden_5cor", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 7, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 585, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 586, + "hotspotLoc": "loc.poh_posh_garden_5mid", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 587, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 588, + "hotspotLoc": "loc.poh_posh_garden_5end", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 589, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 590, + "hotspotLoc": "loc.poh_posh_garden_5end", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 591, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 7, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 592, + "hotspotLoc": "loc.poh_posh_garden_5mid", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 7, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 593, + "hotspotLoc": "loc.poh_posh_garden_4", + "roomKey": "formal_garden", + "name": "Fencing", + "localX": 7, + "localZ": 7, + "level": 0, + "shape": 2, + "rotation": 1 + }, + { + "index": 594, + "hotspotLoc": "loc.poh_posh_garden_5cor", + "roomKey": "formal_garden", + "name": "Hedging", + "localX": 7, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 595, + "hotspotLoc": "loc.poh_cos_room_tresure_chest_hotspot", + "roomKey": "costume_room", + "name": "Treasure chest space", + "localX": 0, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 596, + "hotspotLoc": "loc.poh_cos_room_armour_case_hotspot", + "roomKey": "costume_room", + "name": "Armour case space", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 597, + "hotspotLoc": "loc.poh_cos_room_fancy_dress_box_hotspot", + "roomKey": "costume_room", + "name": "Fancy dress box space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 598, + "hotspotLoc": "loc.poh_cos_room_magic_wardrobe_hotspot", + "roomKey": "costume_room", + "name": "Magic wardrobe space", + "localX": 3, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 599, + "hotspotLoc": "loc.poh_cos_room_cape_rack_hotspot", + "roomKey": "costume_room", + "name": "Cape rack space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 600, + "hotspotLoc": "loc.poh_cos_room_toy_box_hotspot", + "roomKey": "costume_room", + "name": "Toy box space", + "localX": 7, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 601, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 2, + "rotation": 3 + }, + { + "index": 602, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 603, + "hotspotLoc": "loc.poh_superior_garden_hotspot_seating_a_left", + "roomKey": "superior_garden", + "name": "Seating space", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 604, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_post_m", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 605, + "hotspotLoc": "loc.poh_superior_garden_hotspot_seating_a_right", + "roomKey": "superior_garden", + "name": "Seating space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 606, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_post", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 607, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 608, + "hotspotLoc": "loc.poh_superior_garden_hotspot_topiary", + "roomKey": "superior_garden", + "name": "Topiary space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 609, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 0, + "localZ": 7, + "level": 0, + "shape": 2, + "rotation": 0 + }, + { + "index": 610, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 611, + "hotspotLoc": "loc.poh_superior_garden_hotspot_seating_b_right", + "roomKey": "superior_garden", + "name": "Seating space", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 612, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 1, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 613, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_post", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 614, + "hotspotLoc": "loc.poh_superior_garden_hotspot_seating_b_left", + "roomKey": "superior_garden", + "name": "Seating space", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 615, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_post_m", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 616, + "hotspotLoc": "loc.poh_superior_garden_hotspot_treering", + "roomKey": "superior_garden", + "name": "Teleport space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 617, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_post_m", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 5, + "localZ": 0, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 618, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_post", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 619, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 620, + "hotspotLoc": "loc.poh_superior_garden_hotspot_pool", + "roomKey": "superior_garden", + "name": "Pool space", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 621, + "hotspotLoc": "loc.poh_superior_garden_hotspot_theme_feature", + "roomKey": "superior_garden", + "name": "Theme space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 622, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 623, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 7, + "localZ": 0, + "level": 0, + "shape": 2, + "rotation": 2 + }, + { + "index": 624, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 625, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_post", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 626, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_post_m", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 627, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 7, + "localZ": 6, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 628, + "hotspotLoc": "loc.poh_superior_garden_hotspot_fence_middle", + "roomKey": "superior_garden", + "name": "Fence space", + "localX": 7, + "localZ": 7, + "level": 0, + "shape": 2, + "rotation": 1 + }, + { + "index": 629, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_corner", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 630, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 0, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 631, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 632, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 0, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 633, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 0, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 634, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 635, + "hotspotLoc": "loc.poh_menagerie_petfeeder_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Pet feeder space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 636, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_corner", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 0, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 637, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 638, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 3, + "rotation": 1 + }, + { + "index": 639, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 640, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 641, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 642, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 0, + "rotation": 2 + }, + { + "index": 643, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 644, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 3, + "rotation": 2 + }, + { + "index": 645, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 646, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 647, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 648, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 649, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 0, + "rotation": 1 + }, + { + "index": 650, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 651, + "hotspotLoc": "loc.poh_menagerie_combatring_mat_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 652, + "hotspotLoc": "loc.poh_menagerie_combatring_mat_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 653, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 0, + "rotation": 3 + }, + { + "index": 654, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 655, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 656, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 657, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 3, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 658, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 3, + "rotation": 0 + }, + { + "index": 659, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 660, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 661, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 662, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 0, + "rotation": 0 + }, + { + "index": 663, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 664, + "hotspotLoc": "loc.poh_menagerie_combatring_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Arena space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 3, + "rotation": 3 + }, + { + "index": 665, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 666, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 667, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 668, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 4, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 669, + "hotspotLoc": "loc.poh_menagerie_habitat_feature", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 670, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 671, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 672, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 673, + "hotspotLoc": "loc.poh_menagerie_pethouse_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Pet house space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 674, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 675, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 676, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 677, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 5, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 678, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 679, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 680, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 681, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 682, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 683, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 684, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 685, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 686, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 687, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 688, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 689, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_middle", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 690, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 691, + "hotspotLoc": "loc.poh_menagerie_scratchingpost_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Scratching post space", + "localX": 6, + "localZ": 7, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 692, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_corner", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 7, + "localZ": 0, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 693, + "hotspotLoc": "loc.poh_menagerie_petlist_hotspot", + "roomKey": "menagerie_outdoor", + "name": "Pet list space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 2 + }, + { + "index": 694, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 7, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 695, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 696, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 7, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 697, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 7, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 698, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_side", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 699, + "hotspotLoc": "loc.poh_menagerie_habitat_ground_corner", + "roomKey": "menagerie_outdoor", + "name": "Habitat space", + "localX": 7, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 700, + "hotspotLoc": "loc.poh_achievement_hotspot_log", + "roomKey": "achievement_gallery", + "name": "Adventure log space", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 701, + "hotspotLoc": "loc.poh_achievement_hotspot_questlist", + "roomKey": "achievement_gallery", + "name": "Quest list space", + "localX": 0, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 702, + "hotspotLoc": "loc.poh_achievement_hotspot_lair", + "roomKey": "achievement_gallery", + "name": "Boss lair space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 10, + "rotation": 3 + }, + { + "index": 703, + "hotspotLoc": "loc.poh_achievement_hotspot_jewellerybox", + "roomKey": "achievement_gallery", + "name": "Jewellery box space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 704, + "hotspotLoc": "loc.poh_achievement_hotspot_altar", + "roomKey": "achievement_gallery", + "name": "Altar space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 705, + "hotspotLoc": "loc.poh_achievement_hotspot_display", + "roomKey": "achievement_gallery", + "name": "Display space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 1 + }, + { + "index": 706, + "hotspotLoc": "loc.poh_telenexus_3", + "roomKey": "portal_nexus", + "name": "Curtain space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 707, + "hotspotLoc": "loc.poh_telenexus_3", + "roomKey": "portal_nexus", + "name": "Curtain space", + "localX": 0, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 0 + }, + { + "index": 708, + "hotspotLoc": "loc.poh_nexus_4_amulet", + "roomKey": "portal_nexus", + "name": "Amulet space", + "localX": 1, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 709, + "hotspotLoc": "loc.poh_telenexus_2_corner", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 1, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 710, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 711, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 712, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 713, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 714, + "hotspotLoc": "loc.poh_telenexus_2_corner", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 715, + "hotspotLoc": "loc.poh_telenexus_3", + "roomKey": "portal_nexus", + "name": "Curtain space", + "localX": 2, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 716, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 2, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 717, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 718, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 719, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 720, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 721, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 2, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 722, + "hotspotLoc": "loc.poh_telenexus_3", + "roomKey": "portal_nexus", + "name": "Curtain space", + "localX": 2, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 723, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 3, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 724, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 725, + "hotspotLoc": "loc.poh_telenexus_1", + "roomKey": "portal_nexus", + "name": "Portal Nexus space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 726, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 727, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 728, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 4, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 729, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 730, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 731, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 4, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 732, + "hotspotLoc": "loc.poh_telenexus_3", + "roomKey": "portal_nexus", + "name": "Curtain space", + "localX": 5, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 733, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 5, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 734, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 735, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 736, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 737, + "hotspotLoc": "loc.poh_telenexus_2_middle", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 738, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 739, + "hotspotLoc": "loc.poh_telenexus_3", + "roomKey": "portal_nexus", + "name": "Curtain space", + "localX": 5, + "localZ": 7, + "level": 0, + "shape": 5, + "rotation": 1 + }, + { + "index": 740, + "hotspotLoc": "loc.poh_nexus_5_amulet", + "roomKey": "portal_nexus", + "name": "Amulet space", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 5, + "rotation": 3 + }, + { + "index": 741, + "hotspotLoc": "loc.poh_telenexus_2_corner", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 6, + "localZ": 1, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 742, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 743, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 744, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 745, + "hotspotLoc": "loc.poh_telenexus_2_side", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 746, + "hotspotLoc": "loc.poh_telenexus_2_corner", + "roomKey": "portal_nexus", + "name": "Rug space", + "localX": 6, + "localZ": 6, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 747, + "hotspotLoc": "loc.poh_telenexus_3", + "roomKey": "portal_nexus", + "name": "Curtain space", + "localX": 7, + "localZ": 2, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 748, + "hotspotLoc": "loc.poh_telenexus_3", + "roomKey": "portal_nexus", + "name": "Curtain space", + "localX": 7, + "localZ": 5, + "level": 0, + "shape": 5, + "rotation": 2 + }, + { + "index": 749, + "hotspotLoc": "loc.poh_leaguehall_statue_hotspot", + "roomKey": "league_hall", + "name": "Statue space", + "localX": 0, + "localZ": 0, + "level": 0, + "shape": 11, + "rotation": 2 + }, + { + "index": 750, + "hotspotLoc": "loc.poh_leaguehall_accomplishment_scroll_hotspot", + "roomKey": "league_hall", + "name": "Accomplishment scroll space", + "localX": 0, + "localZ": 2, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 751, + "hotspotLoc": "loc.poh_leaguehall_bannerstand_hotspot", + "roomKey": "league_hall", + "name": "Banner stand space", + "localX": 0, + "localZ": 7, + "level": 0, + "shape": 11, + "rotation": 3 + }, + { + "index": 752, + "hotspotLoc": "loc.poh_leaguehall_rug_corner_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 1, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 753, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 1, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 754, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 1, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 3 + }, + { + "index": 755, + "hotspotLoc": "loc.poh_leaguehall_rug_corner_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 1, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 756, + "hotspotLoc": "loc.poh_leaguehall_pedestal_hotspot_1", + "roomKey": "league_hall", + "name": "Pedestal space", + "localX": 1, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 757, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 2, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 758, + "hotspotLoc": "loc.poh_leaguehall_rug_middle_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 2, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 759, + "hotspotLoc": "loc.poh_leaguehall_rug_middle_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 2, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 760, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 2, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 761, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 3, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 762, + "hotspotLoc": "loc.poh_leaguehall_rug_middle_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 3, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 763, + "hotspotLoc": "loc.poh_leaguehall_rug_middle_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 3, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 764, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 3, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 765, + "hotspotLoc": "loc.poh_leaguehall_pedestal_hotspot_2", + "roomKey": "league_hall", + "name": "Pedestal space", + "localX": 3, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 766, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 4, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 767, + "hotspotLoc": "loc.poh_leaguehall_rug_middle_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 4, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 768, + "hotspotLoc": "loc.poh_leaguehall_rug_middle_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 4, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 769, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 4, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 770, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 5, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 771, + "hotspotLoc": "loc.poh_leaguehall_rug_middle_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 5, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 772, + "hotspotLoc": "loc.poh_leaguehall_rug_middle_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 5, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 773, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 5, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 0 + }, + { + "index": 774, + "hotspotLoc": "loc.poh_leaguehall_pedestal_hotspot_3", + "roomKey": "league_hall", + "name": "Pedestal space", + "localX": 5, + "localZ": 6, + "level": 0, + "shape": 10, + "rotation": 0 + }, + { + "index": 775, + "hotspotLoc": "loc.poh_leaguehall_trophycase_hotspot", + "roomKey": "league_hall", + "name": "Trophy case space", + "localX": 6, + "localZ": 0, + "level": 0, + "shape": 11, + "rotation": 1 + }, + { + "index": 776, + "hotspotLoc": "loc.poh_leaguehall_rug_corner_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 6, + "localZ": 2, + "level": 0, + "shape": 22, + "rotation": 2 + }, + { + "index": 777, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 6, + "localZ": 3, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 778, + "hotspotLoc": "loc.poh_leaguehall_rug_side_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 6, + "localZ": 4, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 779, + "hotspotLoc": "loc.poh_leaguehall_rug_corner_hotspot", + "roomKey": "league_hall", + "name": "Rug space", + "localX": 6, + "localZ": 5, + "level": 0, + "shape": 22, + "rotation": 1 + }, + { + "index": 780, + "hotspotLoc": "loc.poh_leaguehall_outfitstand_hotspot", + "roomKey": "league_hall", + "name": "Outfit stand space", + "localX": 7, + "localZ": 7, + "level": 0, + "shape": 11, + "rotation": 0 + } + ] +} diff --git a/api/poh/src/main/resources/poh_index_manifest.json b/api/poh/src/main/resources/poh_index_manifest.json new file mode 100644 index 000000000..c6b2607f2 --- /dev/null +++ b/api/poh/src/main/resources/poh_index_manifest.json @@ -0,0 +1,823 @@ +{ + "rooms": [ + "parlour", + "kitchen", + "dining_room", + "bedroom", + "skill_hall_stairs_up", + "skill_hall_stairs_top", + "quest_hall_stairs_up", + "quest_hall_stairs_top", + "workshop", + "chapel", + "study", + "throne_room", + "portal_chamber", + "combat_room", + "games_room", + "treasure_room", + "dungeon_corridor", + "dungeon_stairs", + "dungeon_junction", + "oubliette", + "roof_a", + "roof_b", + "roof_c", + "menagerie_indoor", + "garden", + "formal_garden", + "costume_room", + "grass_filler", + "dungeon_filler", + "superior_garden", + "menagerie_outdoor", + "achievement_gallery", + "portal_nexus", + "league_hall" + ], + "hotspots": [ + "parlour|loc.poh_parlour_5|0|1|0", + "parlour|loc.poh_parlour_7|0|2|0", + "parlour|loc.poh_parlour_7|0|5|0", + "parlour|loc.poh_parlour_7|2|0|0", + "parlour|loc.poh_parlour_4_corner|2|2|0", + "parlour|loc.poh_parlour_4_side|2|3|0", + "parlour|loc.poh_parlour_1|2|4|0", + "parlour|loc.poh_parlour_4_side|2|4|0", + "parlour|loc.poh_parlour_4_corner|2|5|0", + "parlour|loc.poh_parlour_7|2|7|0", + "parlour|loc.poh_parlour_4_side|3|2|0", + "parlour|loc.poh_parlour_4_middle|3|3|0", + "parlour|loc.poh_parlour_4_middle|3|4|0", + "parlour|loc.poh_parlour_4_side|3|5|0", + "parlour|loc.poh_parlour_6|3|7|0", + "parlour|loc.poh_parlour_4_side|4|2|0", + "parlour|loc.poh_parlour_3|4|3|0", + "parlour|loc.poh_parlour_4_middle|4|3|0", + "parlour|loc.poh_parlour_4_middle|4|4|0", + "parlour|loc.poh_parlour_4_side|4|5|0", + "parlour|loc.poh_parlour_7|5|0|0", + "parlour|loc.poh_parlour_4_corner|5|2|0", + "parlour|loc.poh_parlour_4_side|5|3|0", + "parlour|loc.poh_parlour_2|5|4|0", + "parlour|loc.poh_parlour_4_side|5|4|0", + "parlour|loc.poh_parlour_4_corner|5|5|0", + "parlour|loc.poh_parlour_7|5|7|0", + "parlour|loc.poh_parlour_5|7|1|0", + "parlour|loc.poh_parlour_7|7|2|0", + "parlour|loc.poh_parlour_7|7|5|0", + "kitchen|loc.poh_kitchen_4|0|0|0", + "kitchen|loc.poh_kitchen_3|0|6|0", + "kitchen|loc.poh_kitchen_8|1|0|0", + "kitchen|loc.poh_kitchen_2_crockery|1|7|0", + "kitchen|loc.poh_kitchen_7|3|3|0", + "kitchen|loc.poh_kitchen_1|3|7|0", + "kitchen|loc.poh_kitchen_5|6|0|0", + "kitchen|loc.poh_kitchen_2_crockery|6|7|0", + "kitchen|loc.poh_kitchen_6|7|3|0", + "kitchen|loc.poh_kitchen_2|7|6|0", + "dining_room|loc.poh_dining_room_7|0|0|0", + "dining_room|loc.poh_dining_room_5|0|2|0", + "dining_room|loc.poh_dining_room_5|0|5|0", + "dining_room|loc.poh_dining_room_5|2|0|0", + "dining_room|loc.poh_dining_room_3|2|2|0", + "dining_room|loc.poh_dining_room_1|2|3|0", + "dining_room|loc.poh_dining_room_2|2|5|0", + "dining_room|loc.poh_dining_room_6|2|7|0", + "dining_room|loc.poh_dining_room_3|3|2|0", + "dining_room|loc.poh_dining_room_2|3|5|0", + "dining_room|loc.poh_dining_room_4|3|7|0", + "dining_room|loc.poh_dining_room_3|4|2|0", + "dining_room|loc.poh_dining_room_2|4|5|0", + "dining_room|loc.poh_dining_room_5|5|0|0", + "dining_room|loc.poh_dining_room_3|5|2|0", + "dining_room|loc.poh_dining_room_2|5|5|0", + "dining_room|loc.poh_dining_room_6|5|7|0", + "dining_room|loc.poh_dining_room_5|7|2|0", + "dining_room|loc.poh_dining_room_5|7|5|0", + "bedroom|loc.poh_bedroom_4|0|2|0", + "bedroom|loc.poh_bedroom_4|0|5|0", + "bedroom|loc.poh_bedroom_3|0|7|0", + "bedroom|loc.poh_bedroom_5_corner|1|1|0", + "bedroom|loc.poh_bedroom_5_side|1|2|0", + "bedroom|loc.poh_bedroom_5_side|1|3|0", + "bedroom|loc.poh_bedroom_5_corner|1|4|0", + "bedroom|loc.poh_bedroom_4|2|0|0", + "bedroom|loc.poh_bedroom_5_side|2|1|0", + "bedroom|loc.poh_bedroom_5_middle|2|2|0", + "bedroom|loc.poh_bedroom_5_middle|2|3|0", + "bedroom|loc.poh_bedroom_5_side|2|4|0", + "bedroom|loc.poh_bedroom_4|2|7|0", + "bedroom|loc.poh_bedroom_5_side|3|1|0", + "bedroom|loc.poh_bedroom_5_middle|3|2|0", + "bedroom|loc.poh_bedroom_5_middle|3|3|0", + "bedroom|loc.poh_bedroom_5_side|3|4|0", + "bedroom|loc.poh_bedroom_1_doublebed|3|6|0", + "bedroom|loc.poh_bedroom_5_side|4|1|0", + "bedroom|loc.poh_bedroom_5_middle|4|2|0", + "bedroom|loc.poh_bedroom_5_middle|4|3|0", + "bedroom|loc.poh_bedroom_5_side|4|4|0", + "bedroom|loc.poh_bedroom_4|5|0|0", + "bedroom|loc.poh_bedroom_5_side|5|1|0", + "bedroom|loc.poh_bedroom_5_middle|5|2|0", + "bedroom|loc.poh_bedroom_5_middle|5|3|0", + "bedroom|loc.poh_bedroom_5_side|5|4|0", + "bedroom|loc.poh_bedroom_4|5|7|0", + "bedroom|loc.poh_bedroom_5_corner|6|1|0", + "bedroom|loc.poh_bedroom_5_side|6|2|0", + "bedroom|loc.poh_bedroom_5_side|6|3|0", + "bedroom|loc.poh_bedroom_5_corner|6|4|0", + "bedroom|loc.poh_bedroom_2|6|7|0", + "bedroom|loc.poh_bedroom_7|7|0|0", + "bedroom|loc.poh_bedroom_4|7|2|0", + "bedroom|loc.poh_bedroom_6|7|3|0", + "bedroom|loc.poh_bedroom_4|7|5|0", + "skill_hall_stairs_up|loc.poh_hall1_7|0|6|0", + "skill_hall_stairs_up|loc.poh_hall1_1_corner|1|1|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|1|2|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|1|3|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|1|4|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|1|5|0", + "skill_hall_stairs_up|loc.poh_hall1_1_corner|1|6|0", + "skill_hall_stairs_up|loc.poh_hall1_4|1|7|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|2|1|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|2|2|0", + "skill_hall_stairs_up|loc.poh_hall1_6|2|3|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|2|3|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|2|4|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|2|5|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|2|6|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|3|1|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|3|2|0", + "skill_hall_stairs_up|loc.poh_hall1_1_stairs_up|3|3|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|3|3|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|3|4|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|3|5|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|3|6|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|4|1|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|4|2|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|4|3|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|4|4|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|4|5|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|4|6|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|5|1|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|5|2|0", + "skill_hall_stairs_up|loc.poh_hall1_5|5|3|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|5|3|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|5|4|0", + "skill_hall_stairs_up|loc.poh_hall1_1_middle|5|5|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|5|6|0", + "skill_hall_stairs_up|loc.poh_hall1_1_corner|6|1|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|6|2|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|6|3|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|6|4|0", + "skill_hall_stairs_up|loc.poh_hall1_1_side|6|5|0", + "skill_hall_stairs_up|loc.poh_hall1_1_corner|6|6|0", + "skill_hall_stairs_up|loc.poh_hall1_2|6|7|0", + "skill_hall_stairs_top|loc.poh_hall1_7|0|6|0", + "skill_hall_stairs_top|loc.poh_hall1_1_corner|1|1|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|1|2|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|1|3|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|1|4|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|1|5|0", + "skill_hall_stairs_top|loc.poh_hall1_1_corner|1|6|0", + "skill_hall_stairs_top|loc.poh_hall1_4|1|7|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|2|1|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|2|2|0", + "skill_hall_stairs_top|loc.poh_hall1_6|2|3|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|2|3|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|2|4|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|2|5|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|2|6|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|3|1|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|3|2|0", + "skill_hall_stairs_top|loc.poh_hall1_1_stairs_top|3|3|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|3|3|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|3|4|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|3|5|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|3|6|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|4|1|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|4|2|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|4|3|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|4|4|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|4|5|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|4|6|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|5|1|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|5|2|0", + "skill_hall_stairs_top|loc.poh_hall1_5|5|3|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|5|3|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|5|4|0", + "skill_hall_stairs_top|loc.poh_hall1_1_middle|5|5|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|5|6|0", + "skill_hall_stairs_top|loc.poh_hall1_1_corner|6|1|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|6|2|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|6|3|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|6|4|0", + "skill_hall_stairs_top|loc.poh_hall1_1_side|6|5|0", + "skill_hall_stairs_top|loc.poh_hall1_1_corner|6|6|0", + "skill_hall_stairs_top|loc.poh_hall1_2|6|7|0", + "quest_hall_stairs_up|loc.poh_hall2_7|0|1|0", + "quest_hall_stairs_up|loc.poh_hall2_4|0|6|0", + "quest_hall_stairs_up|loc.poh_hall2_1_corner|1|1|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|1|2|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|1|3|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|1|4|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|1|5|0", + "quest_hall_stairs_up|loc.poh_hall2_1_corner|1|6|0", + "quest_hall_stairs_up|loc.poh_hall2_2|1|7|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|2|1|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|2|2|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|2|3|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|2|4|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|2|5|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|2|6|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|3|1|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|3|2|0", + "quest_hall_stairs_up|loc.poh_hall2_1_stairs_up|3|3|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|3|3|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|3|4|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|3|5|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|3|6|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|4|1|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|4|2|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|4|3|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|4|4|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|4|5|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|4|6|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|5|1|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|5|2|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|5|3|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|5|4|0", + "quest_hall_stairs_up|loc.poh_hall2_1_middle|5|5|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|5|6|0", + "quest_hall_stairs_up|loc.poh_hall2_1_corner|6|1|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|6|2|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|6|3|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|6|4|0", + "quest_hall_stairs_up|loc.poh_hall2_1_side|6|5|0", + "quest_hall_stairs_up|loc.poh_hall2_1_corner|6|6|0", + "quest_hall_stairs_up|loc.poh_hall2_3|6|7|0", + "quest_hall_stairs_up|loc.poh_hall2_6|7|1|0", + "quest_hall_stairs_up|loc.poh_hall2_5|7|6|0", + "quest_hall_stairs_top|loc.poh_hall2_7|0|1|0", + "quest_hall_stairs_top|loc.poh_hall2_4|0|6|0", + "quest_hall_stairs_top|loc.poh_hall2_1_corner|1|1|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|1|2|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|1|3|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|1|4|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|1|5|0", + "quest_hall_stairs_top|loc.poh_hall2_1_corner|1|6|0", + "quest_hall_stairs_top|loc.poh_hall2_2|1|7|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|2|1|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|2|2|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|2|3|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|2|4|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|2|5|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|2|6|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|3|1|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|3|2|0", + "quest_hall_stairs_top|loc.poh_hall2_1_stairs_top|3|3|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|3|3|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|3|4|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|3|5|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|3|6|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|4|1|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|4|2|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|4|3|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|4|4|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|4|5|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|4|6|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|5|1|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|5|2|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|5|3|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|5|4|0", + "quest_hall_stairs_top|loc.poh_hall2_1_middle|5|5|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|5|6|0", + "quest_hall_stairs_top|loc.poh_hall2_1_corner|6|1|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|6|2|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|6|3|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|6|4|0", + "quest_hall_stairs_top|loc.poh_hall2_1_side|6|5|0", + "quest_hall_stairs_top|loc.poh_hall2_1_corner|6|6|0", + "quest_hall_stairs_top|loc.poh_hall2_3|6|7|0", + "quest_hall_stairs_top|loc.poh_hall2_6|7|1|0", + "quest_hall_stairs_top|loc.poh_hall2_5|7|6|0", + "workshop|loc.poh_workshop_3c|0|1|0", + "workshop|loc.poh_workshop_2|0|3|0", + "workshop|loc.poh_workshop_3e|0|6|0", + "workshop|loc.poh_workshop_3a|1|0|0", + "workshop|loc.poh_workshop_2_stool|1|3|0", + "workshop|loc.poh_workshop_1_stool|3|3|0", + "workshop|loc.poh_workshop_1|3|4|0", + "workshop|loc.poh_workshop_3b|6|0|0", + "workshop|loc.poh_workshop_3d|7|1|0", + "workshop|loc.poh_workshop_4|7|3|0", + "workshop|loc.poh_workshop_5|7|6|0", + "chapel|loc.poh_chapel_6|0|0|0", + "chapel|loc.poh_chapelwindow_hotspot_rimmington|0|2|0", + "chapel|loc.poh_chapelwindow_hotspot_rimmington|0|5|0", + "chapel|loc.poh_chapel_3|1|5|0", + "chapel|loc.poh_chapelwindow_hotspot_rimmington|2|7|0", + "chapel|loc.poh_chapel_5_corner|3|0|0", + "chapel|loc.poh_chapel_5_side|3|1|0", + "chapel|loc.poh_chapel_5_side|3|2|0", + "chapel|loc.poh_chapel_5_side|3|3|0", + "chapel|loc.poh_chapel_5_corner|3|4|0", + "chapel|loc.poh_chapel_2|3|5|0", + "chapel|loc.poh_chapel_1|3|7|0", + "chapel|loc.poh_chapel_5_corner|4|0|0", + "chapel|loc.poh_chapel_5_side|4|1|0", + "chapel|loc.poh_chapel_5_side|4|2|0", + "chapel|loc.poh_chapel_5_side|4|3|0", + "chapel|loc.poh_chapel_5_corner|4|4|0", + "chapel|loc.poh_chapelwindow_hotspot_rimmington|5|7|0", + "chapel|loc.poh_chapel_3|6|5|0", + "chapel|loc.poh_chapel_6|7|0|0", + "chapel|loc.poh_chapelwindow_hotspot_rimmington|7|2|0", + "chapel|loc.poh_chapel_7|7|3|0", + "chapel|loc.poh_chapelwindow_hotspot_rimmington|7|5|0", + "study|loc.poh_study_5|0|1|0", + "study|loc.poh_study_2|1|4|0", + "study|loc.poh_study_5|1|7|0", + "study|loc.poh_study_1|2|2|0", + "study|loc.poh_study_7|3|7|0", + "study|loc.poh_study_7|4|7|0", + "study|loc.poh_study_4|5|2|0", + "study|loc.poh_study_6|5|7|0", + "study|loc.poh_study_5|6|7|0", + "study|loc.poh_study_5|7|1|0", + "throne_room|loc.poh_throne_room_5|0|0|0", + "throne_room|loc.poh_throne_room_5|0|1|0", + "throne_room|loc.poh_throne_room_5|0|2|0", + "throne_room|loc.poh_throne_room_5|0|3|0", + "throne_room|loc.poh_throne_room_5|0|4|0", + "throne_room|loc.poh_throne_room_5|0|5|0", + "throne_room|loc.poh_throne_room_7|1|6|0", + "throne_room|loc.poh_throne_room_3_rimmington|3|3|0", + "throne_room|loc.poh_throne_room_3_rimmington|3|4|0", + "throne_room|loc.poh_throne_room_1|3|6|0", + "throne_room|loc.poh_throne_room_3_q|3|7|0", + "throne_room|loc.poh_throne_room_3_rimmington|4|3|0", + "throne_room|loc.poh_throne_room_3_rimmington|4|4|0", + "throne_room|loc.poh_throne_room_1|4|6|0", + "throne_room|loc.poh_throne_room_3_q|4|7|0", + "throne_room|loc.poh_throne_room_4|6|6|0", + "throne_room|loc.poh_throne_room_6|7|0|0", + "throne_room|loc.poh_throne_room_6|7|1|0", + "throne_room|loc.poh_throne_room_6|7|2|0", + "throne_room|loc.poh_throne_room_6|7|3|0", + "throne_room|loc.poh_throne_room_6|7|4|0", + "throne_room|loc.poh_throne_room_6|7|5|0", + "portal_chamber|loc.poh_teleroom_1|0|3|0", + "portal_chamber|loc.poh_teleroom_7|3|3|0", + "portal_chamber|loc.poh_teleroom_2|3|7|0", + "portal_chamber|loc.poh_teleroom_3|7|3|0", + "combat_room|loc.poh_combat_room_6|1|0|0", + "combat_room|loc.poh_gr_1_wall_cobat_corner|1|1|0", + "combat_room|loc.poh_gr_1_wall_combat|1|2|0", + "combat_room|loc.poh_gr_1_wall_combat|1|3|0", + "combat_room|loc.poh_gr_1_wall_combat|1|4|0", + "combat_room|loc.poh_gr_1_wall_everything|1|5|0", + "combat_room|loc.poh_gr_1_wall_redcorner|1|6|0", + "combat_room|loc.poh_combat_room_5|1|7|0", + "combat_room|loc.poh_gr_1_wall_combat|2|1|0", + "combat_room|loc.poh_gr_1_floor_sw|2|2|0", + "combat_room|loc.poh_gr_1_floor_side|2|3|0", + "combat_room|loc.poh_gr_1_wall_ranging_agility|2|4|0", + "combat_room|loc.poh_gr_1_floor_side|2|4|0", + "combat_room|loc.poh_gr_1_floor_nw|2|5|0", + "combat_room|loc.poh_gr_1_wall_everything|2|6|0", + "combat_room|loc.poh_gr_1_wall_combat|3|1|0", + "combat_room|loc.poh_gr_1_floor_s|3|2|0", + "combat_room|loc.poh_gr_1_floor_middle|3|3|0", + "combat_room|loc.poh_gr_1_wall_agility|3|4|0", + "combat_room|loc.poh_gr_1_floor_middle|3|4|0", + "combat_room|loc.poh_gr_1_wall_ranging|3|5|0", + "combat_room|loc.poh_gr_1_floor_n|3|5|0", + "combat_room|loc.poh_gr_1_wall_combat_agility|3|6|0", + "combat_room|loc.poh_combat_room_4|3|7|0", + "combat_room|loc.poh_gr_1_wall_combat|4|1|0", + "combat_room|loc.poh_gr_1_wall_ranging|4|2|0", + "combat_room|loc.poh_gr_1_floor_s|4|2|0", + "combat_room|loc.poh_gr_1_floor_middle|4|3|0", + "combat_room|loc.poh_gr_1_wall_agility|4|4|0", + "combat_room|loc.poh_gr_1_floor_middle|4|4|0", + "combat_room|loc.poh_gr_1_floor_n|4|5|0", + "combat_room|loc.poh_gr_1_wall_combat_agility|4|6|0", + "combat_room|loc.poh_gr_1_wall_ranging_combat|5|1|0", + "combat_room|loc.poh_gr_1_floor_se|5|2|0", + "combat_room|loc.poh_gr_1_wall_ranging|5|3|0", + "combat_room|loc.poh_gr_1_floor_side|5|3|0", + "combat_room|loc.poh_gr_1_wall_agility|5|4|0", + "combat_room|loc.poh_gr_1_floor_side|5|4|0", + "combat_room|loc.poh_gr_1_floor_ne|5|5|0", + "combat_room|loc.poh_gr_1_wall_combat_agility|5|6|0", + "combat_room|loc.poh_gr_1_wall_bluecorner|6|1|0", + "combat_room|loc.poh_gr_1_wall_ranging_combat|6|2|0", + "combat_room|loc.poh_gr_1_wall_combat|6|3|0", + "combat_room|loc.poh_gr_1_wall_combat|6|4|0", + "combat_room|loc.poh_gr_1_wall_combat_agility|6|5|0", + "combat_room|loc.poh_gr_1_wall_combat_agility_corner|6|6|0", + "combat_room|loc.poh_combat_room_5|6|7|0", + "games_room|loc.poh_games_room_7|1|0|0", + "games_room|loc.poh_games_room_5|2|4|0", + "games_room|loc.poh_games_room_4|3|7|0", + "games_room|loc.poh_games_room_6|5|4|0", + "games_room|loc.poh_games_room_2|6|0|0", + "treasure_room|loc.poh_dungeon_7|1|2|0", + "treasure_room|loc.poh_dungeon_6|1|5|0", + "treasure_room|loc.poh_dungeon_6|2|1|0", + "treasure_room|loc.poh_dungeon_treasure_1|2|6|0", + "treasure_room|loc.poh_dungeon_4r|3|1|0", + "treasure_room|loc.poh_dungeon_treasure_2|3|3|0", + "treasure_room|loc.poh_dungeon_treasure_5|3|6|0", + "treasure_room|loc.poh_dungeon_4l|4|1|0", + "treasure_room|loc.poh_dungeon_treasure_5|4|6|0", + "treasure_room|loc.poh_dungeon_6|5|1|0", + "treasure_room|loc.poh_dungeon_7|6|2|0", + "treasure_room|loc.poh_dungeon_6|6|5|0", + "dungeon_corridor|loc.poh_dungeon_7|1|3|0", + "dungeon_corridor|loc.poh_dungeon_6|1|4|0", + "dungeon_corridor|loc.poh_dungeon_5r|3|1|0", + "dungeon_corridor|loc.poh_dungeon_6|3|1|0", + "dungeon_corridor|loc.poh_dungeon_3|3|2|0", + "dungeon_corridor|loc.poh_dungeon_1|3|3|0", + "dungeon_corridor|loc.poh_dungeon_2|3|5|0", + "dungeon_corridor|loc.poh_dungeon_4l|3|6|0", + "dungeon_corridor|loc.poh_dungeon_5l|4|1|0", + "dungeon_corridor|loc.poh_dungeon_3|4|2|0", + "dungeon_corridor|loc.poh_dungeon_2|4|5|0", + "dungeon_corridor|loc.poh_dungeon_4r|4|6|0", + "dungeon_corridor|loc.poh_dungeon_6|4|6|0", + "dungeon_corridor|loc.poh_dungeon_6|6|3|0", + "dungeon_corridor|loc.poh_dungeon_7|6|4|0", + "dungeon_stairs|loc.poh_dungeon_stairs_3|1|1|0", + "dungeon_stairs|loc.poh_dungeon_6|1|2|0", + "dungeon_stairs|loc.poh_dungeon_6|1|5|0", + "dungeon_stairs|loc.poh_dungeon_7|1|6|0", + "dungeon_stairs|loc.poh_dungeon_6|2|1|0", + "dungeon_stairs|loc.poh_hall1_1_corner|2|2|0", + "dungeon_stairs|loc.poh_hall1_1_side|2|3|0", + "dungeon_stairs|loc.poh_hall1_1_side|2|4|0", + "dungeon_stairs|loc.poh_hall1_1_corner|2|5|0", + "dungeon_stairs|loc.poh_dungeon_6|2|6|0", + "dungeon_stairs|loc.poh_dungeon_5r|3|1|0", + "dungeon_stairs|loc.poh_hall1_1_side|3|2|0", + "dungeon_stairs|loc.poh_hall1_1_stairs_up|3|3|0", + "dungeon_stairs|loc.poh_hall1_1_middle|3|3|0", + "dungeon_stairs|loc.poh_hall1_1_middle|3|4|0", + "dungeon_stairs|loc.poh_hall1_1_side|3|5|0", + "dungeon_stairs|loc.poh_dungeon_4l|3|6|0", + "dungeon_stairs|loc.poh_dungeon_5l|4|1|0", + "dungeon_stairs|loc.poh_hall1_1_side|4|2|0", + "dungeon_stairs|loc.poh_hall1_1_middle|4|3|0", + "dungeon_stairs|loc.poh_hall1_1_middle|4|4|0", + "dungeon_stairs|loc.poh_hall1_1_side|4|5|0", + "dungeon_stairs|loc.poh_dungeon_4r|4|6|0", + "dungeon_stairs|loc.poh_dungeon_6|5|1|0", + "dungeon_stairs|loc.poh_hall1_1_corner|5|2|0", + "dungeon_stairs|loc.poh_hall1_1_side|5|3|0", + "dungeon_stairs|loc.poh_hall1_1_side|5|4|0", + "dungeon_stairs|loc.poh_dungeon_stairs_2|5|5|0", + "dungeon_stairs|loc.poh_hall1_1_corner|5|5|0", + "dungeon_stairs|loc.poh_dungeon_6|5|6|0", + "dungeon_stairs|loc.poh_dungeon_7|6|1|0", + "dungeon_stairs|loc.poh_dungeon_6|6|2|0", + "dungeon_stairs|loc.poh_dungeon_6|6|5|0", + "dungeon_junction|loc.poh_dungeon_5r|3|1|0", + "dungeon_junction|loc.poh_dungeon_6|3|1|0", + "dungeon_junction|loc.poh_dungeon_3|3|2|0", + "dungeon_junction|loc.poh_dungeon_1|3|3|0", + "dungeon_junction|loc.poh_dungeon_7|3|4|0", + "dungeon_junction|loc.poh_dungeon_2|3|5|0", + "dungeon_junction|loc.poh_dungeon_4l|3|6|0", + "dungeon_junction|loc.poh_dungeon_6|3|6|0", + "dungeon_junction|loc.poh_dungeon_5l|4|1|0", + "dungeon_junction|loc.poh_dungeon_6|4|1|0", + "dungeon_junction|loc.poh_dungeon_3|4|2|0", + "dungeon_junction|loc.poh_dungeon_7|4|3|0", + "dungeon_junction|loc.poh_dungeon_2|4|5|0", + "dungeon_junction|loc.poh_dungeon_4r|4|6|0", + "dungeon_junction|loc.poh_dungeon_6|4|6|0", + "oubliette|loc.poh_oubliette_3|0|0|0", + "oubliette|loc.poh_dungeon_7|0|2|0", + "oubliette|loc.poh_oubliette_4|0|5|0", + "oubliette|loc.poh_oubliette_5|1|6|0", + "oubliette|loc.poh_oubliette_4|2|0|0", + "oubliette|loc.poh_oubliette_2_front|2|2|0", + "oubliette|loc.poh_oubliette_1_type8|2|2|0", + "oubliette|loc.poh_oubliette_1_corner|2|2|0", + "oubliette|loc.poh_oubliette_2_front|2|3|0", + "oubliette|loc.poh_oubliette_1_type8|2|3|0", + "oubliette|loc.poh_oubliette_1_side|2|3|0", + "oubliette|loc.poh_oubliette_2_front|2|4|0", + "oubliette|loc.poh_oubliette_1_type8|2|4|0", + "oubliette|loc.poh_oubliette_1_side|2|4|0", + "oubliette|loc.poh_oubliette_2_front|2|5|0", + "oubliette|loc.poh_oubliette_1_type8|2|5|0", + "oubliette|loc.poh_oubliette_1_corner|2|5|0", + "oubliette|loc.poh_dungeon_7|2|7|0", + "oubliette|loc.poh_oubliette_2_front|3|2|0", + "oubliette|loc.poh_oubliette_1_type8|3|2|0", + "oubliette|loc.poh_oubliette_1_side|3|2|0", + "oubliette|loc.poh_oubliette_1_type8|3|3|0", + "oubliette|loc.poh_oubliette_1|3|3|0", + "oubliette|loc.poh_oubliette_1_type8|3|4|0", + "oubliette|loc.poh_oubliette_1|3|4|0", + "oubliette|loc.poh_oubliette_2_door|3|5|0", + "oubliette|loc.poh_oubliette_1_type8|3|5|0", + "oubliette|loc.poh_oubliette_1_side|3|5|0", + "oubliette|loc.poh_oubliette_2_front|4|2|0", + "oubliette|loc.poh_oubliette_1_type8|4|2|0", + "oubliette|loc.poh_oubliette_1_side|4|2|0", + "oubliette|loc.poh_oubliette_1_type8_ogre|4|3|0", + "oubliette|loc.poh_oubliette_1|4|3|0", + "oubliette|loc.poh_oubliette_1_type8|4|4|0", + "oubliette|loc.poh_oubliette_1|4|4|0", + "oubliette|loc.poh_oubliette_2_front|4|5|0", + "oubliette|loc.poh_oubliette_1_type8|4|5|0", + "oubliette|loc.poh_oubliette_1_side|4|5|0", + "oubliette|loc.poh_dungeon_7|5|0|0", + "oubliette|loc.poh_oubliette_2_front|5|2|0", + "oubliette|loc.poh_oubliette_1_type8|5|2|0", + "oubliette|loc.poh_oubliette_1_corner|5|2|0", + "oubliette|loc.poh_oubliette_2_front|5|3|0", + "oubliette|loc.poh_oubliette_1_type8|5|3|0", + "oubliette|loc.poh_oubliette_1_side|5|3|0", + "oubliette|loc.poh_oubliette_2_front|5|4|0", + "oubliette|loc.poh_oubliette_1_type8|5|4|0", + "oubliette|loc.poh_oubliette_1_side|5|4|0", + "oubliette|loc.poh_oubliette_2_front|5|5|0", + "oubliette|loc.poh_oubliette_1_type8|5|5|0", + "oubliette|loc.poh_oubliette_1_corner|5|5|0", + "oubliette|loc.poh_oubliette_4|5|7|0", + "oubliette|loc.poh_oubliette_4|7|2|0", + "oubliette|loc.poh_dungeon_7|7|5|0", + "menagerie_indoor|loc.poh_menagerie_petfeeder_hotspot|0|6|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|1|1|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|1|2|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|1|3|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|1|4|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|2|1|0", + "menagerie_indoor|loc.poh_menagerie_combatring_mat_hotspot|2|2|0", + "menagerie_indoor|loc.poh_menagerie_combatring_mat_hotspot|2|3|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|2|4|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|3|1|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|3|2|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|3|3|0", + "menagerie_indoor|loc.poh_menagerie_combatring_hotspot|3|4|0", + "menagerie_indoor|loc.poh_menagerie_pethouse_hotspot|4|4|0", + "menagerie_indoor|loc.poh_menagerie_scratchingpost_hotspot|6|7|0", + "menagerie_indoor|loc.poh_menagerie_petlist_hotspot|7|1|0", + "garden|loc.poh_crude_garden_5|0|0|0", + "garden|loc.poh_crude_garden_2|1|5|0", + "garden|loc.poh_crude_garden_6|3|1|0", + "garden|loc.poh_crude_garden_1|3|3|0", + "garden|loc.poh_crude_garden_7|4|5|0", + "garden|loc.poh_crude_garden_4|6|0|0", + "garden|loc.poh_garden_8|6|5|0", + "garden|loc.poh_crude_garden_3|6|6|0", + "formal_garden|loc.poh_posh_garden_4|0|0|0", + "formal_garden|loc.poh_posh_garden_5cor|0|0|0", + "formal_garden|loc.poh_posh_garden_4|0|1|0", + "formal_garden|loc.poh_posh_garden_5mid|0|1|0", + "formal_garden|loc.poh_posh_garden_4|0|2|0", + "formal_garden|loc.poh_posh_garden_5end|0|2|0", + "formal_garden|loc.poh_posh_garden_4|0|5|0", + "formal_garden|loc.poh_posh_garden_5end|0|5|0", + "formal_garden|loc.poh_posh_garden_4|0|6|0", + "formal_garden|loc.poh_posh_garden_5mid|0|6|0", + "formal_garden|loc.poh_posh_garden_4|0|7|0", + "formal_garden|loc.poh_posh_garden_5cor|0|7|0", + "formal_garden|loc.poh_posh_garden_4|1|0|0", + "formal_garden|loc.poh_posh_garden_5mid|1|0|0", + "formal_garden|loc.poh_posh_garden_3|1|1|0", + "formal_garden|loc.poh_posh_garden_7|1|2|0", + "formal_garden|loc.poh_posh_garden_6|1|5|0", + "formal_garden|loc.poh_posh_garden_2|1|6|0", + "formal_garden|loc.poh_posh_garden_4|1|7|0", + "formal_garden|loc.poh_posh_garden_5mid|1|7|0", + "formal_garden|loc.poh_posh_garden_4|2|0|0", + "formal_garden|loc.poh_posh_garden_5end|2|0|0", + "formal_garden|loc.poh_posh_garden_7|2|1|0", + "formal_garden|loc.poh_posh_garden_6|2|6|0", + "formal_garden|loc.poh_posh_garden_4|2|7|0", + "formal_garden|loc.poh_posh_garden_5end|2|7|0", + "formal_garden|loc.poh_posh_garden_1|3|3|0", + "formal_garden|loc.poh_posh_garden_4|5|0|0", + "formal_garden|loc.poh_posh_garden_5end|5|0|0", + "formal_garden|loc.poh_posh_garden_6|5|1|0", + "formal_garden|loc.poh_posh_garden_7|5|6|0", + "formal_garden|loc.poh_posh_garden_4|5|7|0", + "formal_garden|loc.poh_posh_garden_5end|5|7|0", + "formal_garden|loc.poh_posh_garden_4|6|0|0", + "formal_garden|loc.poh_posh_garden_5mid|6|0|0", + "formal_garden|loc.poh_posh_garden_2|6|1|0", + "formal_garden|loc.poh_posh_garden_6|6|2|0", + "formal_garden|loc.poh_garden_8|6|4|0", + "formal_garden|loc.poh_posh_garden_7|6|5|0", + "formal_garden|loc.poh_posh_garden_3|6|6|0", + "formal_garden|loc.poh_posh_garden_4|6|7|0", + "formal_garden|loc.poh_posh_garden_5mid|6|7|0", + "formal_garden|loc.poh_posh_garden_4|7|0|0", + "formal_garden|loc.poh_posh_garden_5cor|7|0|0", + "formal_garden|loc.poh_posh_garden_4|7|1|0", + "formal_garden|loc.poh_posh_garden_5mid|7|1|0", + "formal_garden|loc.poh_posh_garden_4|7|2|0", + "formal_garden|loc.poh_posh_garden_5end|7|2|0", + "formal_garden|loc.poh_posh_garden_4|7|5|0", + "formal_garden|loc.poh_posh_garden_5end|7|5|0", + "formal_garden|loc.poh_posh_garden_4|7|6|0", + "formal_garden|loc.poh_posh_garden_5mid|7|6|0", + "formal_garden|loc.poh_posh_garden_4|7|7|0", + "formal_garden|loc.poh_posh_garden_5cor|7|7|0", + "costume_room|loc.poh_cos_room_tresure_chest_hotspot|0|3|0", + "costume_room|loc.poh_cos_room_armour_case_hotspot|2|7|0", + "costume_room|loc.poh_cos_room_fancy_dress_box_hotspot|3|3|0", + "costume_room|loc.poh_cos_room_magic_wardrobe_hotspot|3|7|0", + "costume_room|loc.poh_cos_room_cape_rack_hotspot|6|6|0", + "costume_room|loc.poh_cos_room_toy_box_hotspot|7|3|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|0|0|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|0|1|0", + "superior_garden|loc.poh_superior_garden_hotspot_seating_a_left|0|1|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_post_m|0|2|0", + "superior_garden|loc.poh_superior_garden_hotspot_seating_a_right|0|2|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_post|0|5|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|0|6|0", + "superior_garden|loc.poh_superior_garden_hotspot_topiary|0|6|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|0|7|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|1|0|0", + "superior_garden|loc.poh_superior_garden_hotspot_seating_b_right|1|0|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|1|7|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_post|2|0|0", + "superior_garden|loc.poh_superior_garden_hotspot_seating_b_left|2|0|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_post_m|2|7|0", + "superior_garden|loc.poh_superior_garden_hotspot_treering|3|3|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_post_m|5|0|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_post|5|7|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|6|0|0", + "superior_garden|loc.poh_superior_garden_hotspot_pool|6|0|0", + "superior_garden|loc.poh_superior_garden_hotspot_theme_feature|6|6|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|6|7|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|7|0|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|7|1|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_post|7|2|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_post_m|7|5|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|7|6|0", + "superior_garden|loc.poh_superior_garden_hotspot_fence_middle|7|7|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_corner|0|0|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|0|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|0|2|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|0|3|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|0|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|0|5|0", + "menagerie_outdoor|loc.poh_menagerie_petfeeder_hotspot|0|6|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_corner|0|6|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|1|0|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|1|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|1|1|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|1|2|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|1|2|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|1|3|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|1|3|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|1|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|1|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|1|5|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|1|6|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|2|0|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|2|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|2|1|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_mat_hotspot|2|2|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_mat_hotspot|2|3|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|2|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|2|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|2|5|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|2|6|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|3|0|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|3|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|3|1|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|3|2|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|3|2|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|3|3|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|3|3|0", + "menagerie_outdoor|loc.poh_menagerie_combatring_hotspot|3|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|3|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|3|5|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|3|6|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|4|0|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_feature|4|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|4|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|4|2|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|4|3|0", + "menagerie_outdoor|loc.poh_menagerie_pethouse_hotspot|4|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|4|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|4|5|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|4|6|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|5|0|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|5|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|5|2|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|5|3|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|5|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|5|5|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|5|6|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|6|0|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|6|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|6|2|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|6|3|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|6|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_middle|6|5|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|6|6|0", + "menagerie_outdoor|loc.poh_menagerie_scratchingpost_hotspot|6|7|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_corner|7|0|0", + "menagerie_outdoor|loc.poh_menagerie_petlist_hotspot|7|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|7|1|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|7|2|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|7|3|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|7|4|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_side|7|5|0", + "menagerie_outdoor|loc.poh_menagerie_habitat_ground_corner|7|6|0", + "achievement_gallery|loc.poh_achievement_hotspot_log|0|0|0", + "achievement_gallery|loc.poh_achievement_hotspot_questlist|0|3|0", + "achievement_gallery|loc.poh_achievement_hotspot_lair|0|5|0", + "achievement_gallery|loc.poh_achievement_hotspot_jewellerybox|6|1|0", + "achievement_gallery|loc.poh_achievement_hotspot_altar|6|3|0", + "achievement_gallery|loc.poh_achievement_hotspot_display|6|6|0", + "portal_nexus|loc.poh_telenexus_3|0|2|0", + "portal_nexus|loc.poh_telenexus_3|0|5|0", + "portal_nexus|loc.poh_nexus_4_amulet|1|0|0", + "portal_nexus|loc.poh_telenexus_2_corner|1|1|0", + "portal_nexus|loc.poh_telenexus_2_side|1|2|0", + "portal_nexus|loc.poh_telenexus_2_side|1|3|0", + "portal_nexus|loc.poh_telenexus_2_side|1|4|0", + "portal_nexus|loc.poh_telenexus_2_side|1|5|0", + "portal_nexus|loc.poh_telenexus_2_corner|1|6|0", + "portal_nexus|loc.poh_telenexus_3|2|0|0", + "portal_nexus|loc.poh_telenexus_2_side|2|1|0", + "portal_nexus|loc.poh_telenexus_2_middle|2|2|0", + "portal_nexus|loc.poh_telenexus_2_middle|2|3|0", + "portal_nexus|loc.poh_telenexus_2_middle|2|4|0", + "portal_nexus|loc.poh_telenexus_2_middle|2|5|0", + "portal_nexus|loc.poh_telenexus_2_side|2|6|0", + "portal_nexus|loc.poh_telenexus_3|2|7|0", + "portal_nexus|loc.poh_telenexus_2_side|3|1|0", + "portal_nexus|loc.poh_telenexus_2_middle|3|2|0", + "portal_nexus|loc.poh_telenexus_1|3|3|0", + "portal_nexus|loc.poh_telenexus_2_middle|3|5|0", + "portal_nexus|loc.poh_telenexus_2_side|3|6|0", + "portal_nexus|loc.poh_telenexus_2_side|4|1|0", + "portal_nexus|loc.poh_telenexus_2_middle|4|2|0", + "portal_nexus|loc.poh_telenexus_2_middle|4|5|0", + "portal_nexus|loc.poh_telenexus_2_side|4|6|0", + "portal_nexus|loc.poh_telenexus_3|5|0|0", + "portal_nexus|loc.poh_telenexus_2_side|5|1|0", + "portal_nexus|loc.poh_telenexus_2_middle|5|2|0", + "portal_nexus|loc.poh_telenexus_2_middle|5|3|0", + "portal_nexus|loc.poh_telenexus_2_middle|5|4|0", + "portal_nexus|loc.poh_telenexus_2_middle|5|5|0", + "portal_nexus|loc.poh_telenexus_2_side|5|6|0", + "portal_nexus|loc.poh_telenexus_3|5|7|0", + "portal_nexus|loc.poh_nexus_5_amulet|6|0|0", + "portal_nexus|loc.poh_telenexus_2_corner|6|1|0", + "portal_nexus|loc.poh_telenexus_2_side|6|2|0", + "portal_nexus|loc.poh_telenexus_2_side|6|3|0", + "portal_nexus|loc.poh_telenexus_2_side|6|4|0", + "portal_nexus|loc.poh_telenexus_2_side|6|5|0", + "portal_nexus|loc.poh_telenexus_2_corner|6|6|0", + "portal_nexus|loc.poh_telenexus_3|7|2|0", + "portal_nexus|loc.poh_telenexus_3|7|5|0", + "league_hall|loc.poh_leaguehall_statue_hotspot|0|0|0", + "league_hall|loc.poh_leaguehall_accomplishment_scroll_hotspot|0|2|0", + "league_hall|loc.poh_leaguehall_bannerstand_hotspot|0|7|0", + "league_hall|loc.poh_leaguehall_rug_corner_hotspot|1|2|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|1|3|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|1|4|0", + "league_hall|loc.poh_leaguehall_rug_corner_hotspot|1|5|0", + "league_hall|loc.poh_leaguehall_pedestal_hotspot_1|1|6|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|2|2|0", + "league_hall|loc.poh_leaguehall_rug_middle_hotspot|2|3|0", + "league_hall|loc.poh_leaguehall_rug_middle_hotspot|2|4|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|2|5|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|3|2|0", + "league_hall|loc.poh_leaguehall_rug_middle_hotspot|3|3|0", + "league_hall|loc.poh_leaguehall_rug_middle_hotspot|3|4|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|3|5|0", + "league_hall|loc.poh_leaguehall_pedestal_hotspot_2|3|6|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|4|2|0", + "league_hall|loc.poh_leaguehall_rug_middle_hotspot|4|3|0", + "league_hall|loc.poh_leaguehall_rug_middle_hotspot|4|4|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|4|5|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|5|2|0", + "league_hall|loc.poh_leaguehall_rug_middle_hotspot|5|3|0", + "league_hall|loc.poh_leaguehall_rug_middle_hotspot|5|4|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|5|5|0", + "league_hall|loc.poh_leaguehall_pedestal_hotspot_3|5|6|0", + "league_hall|loc.poh_leaguehall_trophycase_hotspot|6|0|0", + "league_hall|loc.poh_leaguehall_rug_corner_hotspot|6|2|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|6|3|0", + "league_hall|loc.poh_leaguehall_rug_side_hotspot|6|4|0", + "league_hall|loc.poh_leaguehall_rug_corner_hotspot|6|5|0", + "league_hall|loc.poh_leaguehall_outfitstand_hotspot|7|7|0" + ], + "roomsSha256": "c26941aad6989566d917f689d22441a5de3aee0c64eedd8fd9231865a98d5d97", + "hotspotsSha256": "ce02930c7b3d4622b44733073898e99e66084bed8792fe5ea040c59104b73b11" +} diff --git a/api/poh/src/main/resources/poh_rooms.json b/api/poh/src/main/resources/poh_rooms.json new file mode 100644 index 000000000..02b38ce22 --- /dev/null +++ b/api/poh/src/main/resources/poh_rooms.json @@ -0,0 +1,1811 @@ +{ + "styles": [ + { + "index": 0, + "key": "rimmington", + "region": 7534, + "extraRegion": 7535, + "level": 0, + "doorLeftLoc": "loc.poh_hotspot_doorl_rimmington", + "doorRightLoc": "loc.poh_hotspot_doorr_rimmington" + }, + { + "index": 1, + "key": "lumbridge", + "region": 7534, + "extraRegion": 7535, + "level": 1, + "doorLeftLoc": "loc.poh_hotspot_doorl_lumbridge", + "doorRightLoc": "loc.poh_hotspot_doorr_lumbridge" + }, + { + "index": 2, + "key": "pollnivneach", + "region": 7534, + "extraRegion": 7535, + "level": 2, + "doorLeftLoc": "loc.poh_hotspot_doorl_pollnivneach", + "doorRightLoc": "loc.poh_hotspot_doorr_pollnivneach" + }, + { + "index": 3, + "key": "rellekka", + "region": 7534, + "extraRegion": 7535, + "level": 3, + "doorLeftLoc": "loc.poh_hotspot_doorl_rellekka", + "doorRightLoc": "loc.poh_hotspot_doorr_rellekka" + }, + { + "index": 4, + "key": "brimhaven", + "region": 7790, + "extraRegion": 7791, + "level": 0, + "doorLeftLoc": "loc.poh_hotspot_doorl_brimhaven", + "doorRightLoc": "loc.poh_hotspot_doorr_brimhaven" + }, + { + "index": 5, + "key": "yanille", + "region": 7790, + "extraRegion": 7791, + "level": 1, + "doorLeftLoc": "loc.poh_hotspot_doorl_yanille", + "doorRightLoc": "loc.poh_hotspot_doorr_yanille" + }, + { + "index": 6, + "key": "deathly", + "region": 7790, + "extraRegion": 7791, + "level": 2, + "doorLeftLoc": "loc.poh_hotspot_doorl_deathly", + "doorRightLoc": "loc.poh_hotspot_doorr_deathly" + }, + { + "index": 7, + "key": "twisted", + "region": 7790, + "extraRegion": 7791, + "level": 3, + "doorLeftLoc": "loc.poh_hotspot_doorl_twisted", + "doorRightLoc": "loc.poh_hotspot_doorr_twisted" + }, + { + "index": 8, + "key": "hosidius", + "region": 8046, + "extraRegion": 8047, + "level": 0, + "doorLeftLoc": "loc.poh_hotspot_doorl_hosidius", + "doorRightLoc": "loc.poh_hotspot_doorr_hosidius" + }, + { + "index": 9, + "key": "xmas2020", + "region": 8046, + "extraRegion": 8047, + "level": 1, + "doorLeftLoc": "loc.poh_hotspot_doorl_xmas2020", + "doorRightLoc": "loc.poh_hotspot_doorr_xmas2020" + }, + { + "index": 10, + "key": "civitas", + "region": 8046, + "extraRegion": 8047, + "level": 2, + "doorLeftLoc": "loc.poh_hotspot_doorl_civitas", + "doorRightLoc": "loc.poh_hotspot_doorr_civitas" + }, + { + "index": 11, + "key": "canifis", + "region": 8046, + "extraRegion": 8047, + "level": 3, + "doorLeftLoc": "loc.poh_hotspot_doorl_canifis", + "doorRightLoc": "loc.poh_hotspot_doorr_canifis" + }, + { + "index": 12, + "key": "wilderness", + "region": 8302, + "extraRegion": 8303, + "level": 0, + "doorLeftLoc": "loc.poh_hotspot_doorl_lumbridge", + "doorRightLoc": "loc.poh_hotspot_doorr_lumbridge" + } + ], + "rooms": [ + { + "index": 0, + "roomKey": "parlour", + "templateRegion": 7534, + "templateZoneX": 0, + "templateZoneZ": 7, + "doorEdges": [ + "WEST", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 1, + "roomKey": "kitchen", + "templateRegion": 7534, + "templateZoneX": 2, + "templateZoneZ": 7, + "doorEdges": [ + "WEST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + } + ], + "floor": "GROUND" + }, + { + "index": 2, + "roomKey": "dining_room", + "templateRegion": 7534, + "templateZoneX": 4, + "templateZoneZ": 7, + "doorEdges": [ + "WEST", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 3, + "roomKey": "bedroom", + "templateRegion": 7534, + "templateZoneX": 6, + "templateZoneZ": 7, + "doorEdges": [ + "WEST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + } + ], + "floor": "GROUND" + }, + { + "index": 4, + "roomKey": "skill_hall_stairs_up", + "templateRegion": 7534, + "templateZoneX": 1, + "templateZoneZ": 6, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 5, + "roomKey": "skill_hall_stairs_top", + "templateRegion": 7534, + "templateZoneX": 3, + "templateZoneZ": 6, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "UPPER" + }, + { + "index": 6, + "roomKey": "quest_hall_stairs_up", + "templateRegion": 7534, + "templateZoneX": 5, + "templateZoneZ": 6, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 7, + "roomKey": "quest_hall_stairs_top", + "templateRegion": 7534, + "templateZoneX": 7, + "templateZoneZ": 6, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "UPPER" + }, + { + "index": 8, + "roomKey": "workshop", + "templateRegion": 7534, + "templateZoneX": 0, + "templateZoneZ": 5, + "doorEdges": [ + "NORTH", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + } + ], + "floor": "GROUND" + }, + { + "index": 9, + "roomKey": "chapel", + "templateRegion": 7534, + "templateZoneX": 2, + "templateZoneZ": 5, + "doorEdges": [ + "WEST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + } + ], + "floor": "GROUND" + }, + { + "index": 10, + "roomKey": "study", + "templateRegion": 7534, + "templateZoneX": 4, + "templateZoneZ": 5, + "doorEdges": [ + "WEST", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 11, + "roomKey": "throne_room", + "templateRegion": 7534, + "templateZoneX": 6, + "templateZoneZ": 5, + "doorEdges": [ + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + } + ], + "floor": "GROUND" + }, + { + "index": 12, + "roomKey": "portal_chamber", + "templateRegion": 7534, + "templateZoneX": 1, + "templateZoneZ": 4, + "doorEdges": [ + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + } + ], + "floor": "GROUND" + }, + { + "index": 13, + "roomKey": "combat_room", + "templateRegion": 7534, + "templateZoneX": 3, + "templateZoneZ": 4, + "doorEdges": [ + "WEST", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 14, + "roomKey": "games_room", + "templateRegion": 7534, + "templateZoneX": 5, + "templateZoneZ": 4, + "doorEdges": [ + "WEST", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 15, + "roomKey": "treasure_room", + "templateRegion": 7534, + "templateZoneX": 7, + "templateZoneZ": 4, + "doorEdges": [ + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + } + ], + "floor": "DUNGEON" + }, + { + "index": 16, + "roomKey": "dungeon_corridor", + "templateRegion": 7534, + "templateZoneX": 0, + "templateZoneZ": 3, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "DUNGEON" + }, + { + "index": 17, + "roomKey": "dungeon_stairs", + "templateRegion": 7534, + "templateZoneX": 2, + "templateZoneZ": 3, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "DUNGEON" + }, + { + "index": 18, + "roomKey": "dungeon_junction", + "templateRegion": 7534, + "templateZoneX": 4, + "templateZoneZ": 3, + "doorEdges": [ + "NORTH", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 4, + "localZ": 7, + "rotation": 1 + } + ], + "floor": "DUNGEON" + }, + { + "index": 19, + "roomKey": "oubliette", + "templateRegion": 7534, + "templateZoneX": 6, + "templateZoneZ": 3, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "DUNGEON" + }, + { + "index": 20, + "roomKey": "roof_a", + "templateRegion": 7534, + "templateZoneX": 1, + "templateZoneZ": 2, + "doorEdges": [], + "doorPlacements": [], + "floor": "GROUND" + }, + { + "index": 21, + "roomKey": "roof_b", + "templateRegion": 7534, + "templateZoneX": 3, + "templateZoneZ": 2, + "doorEdges": [], + "doorPlacements": [], + "floor": "GROUND" + }, + { + "index": 22, + "roomKey": "roof_c", + "templateRegion": 7534, + "templateZoneX": 5, + "templateZoneZ": 2, + "doorEdges": [], + "doorPlacements": [], + "floor": "GROUND" + }, + { + "index": 23, + "roomKey": "menagerie_indoor", + "templateRegion": 7534, + "templateZoneX": 7, + "templateZoneZ": 2, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 24, + "roomKey": "garden", + "templateRegion": 7534, + "templateZoneX": 0, + "templateZoneZ": 1, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 25, + "roomKey": "formal_garden", + "templateRegion": 7534, + "templateZoneX": 2, + "templateZoneZ": 1, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 26, + "roomKey": "costume_room", + "templateRegion": 7534, + "templateZoneX": 6, + "templateZoneZ": 1, + "doorEdges": [ + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + } + ], + "floor": "GROUND" + }, + { + "index": 27, + "roomKey": "grass_filler", + "templateRegion": 7534, + "templateZoneX": 1, + "templateZoneZ": 0, + "doorEdges": [], + "doorPlacements": [], + "floor": "GROUND" + }, + { + "index": 28, + "roomKey": "dungeon_filler", + "templateRegion": 7534, + "templateZoneX": 3, + "templateZoneZ": 0, + "doorEdges": [], + "doorPlacements": [], + "floor": "DUNGEON" + }, + { + "index": 29, + "roomKey": "superior_garden", + "templateRegion": 7534, + "templateZoneX": 5, + "templateZoneZ": 0, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 30, + "roomKey": "menagerie_outdoor", + "templateRegion": 7534, + "templateZoneX": 7, + "templateZoneZ": 0, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 31, + "roomKey": "achievement_gallery", + "templateRegion": 7535, + "templateZoneX": 1, + "templateZoneZ": 0, + "doorEdges": [ + "NORTH", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + } + ], + "floor": "GROUND" + }, + { + "index": 32, + "roomKey": "portal_nexus", + "templateRegion": 7535, + "templateZoneX": 3, + "templateZoneZ": 0, + "doorEdges": [ + "WEST", + "NORTH", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "RIGHT", + "localX": 3, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "NORTH", + "side": "LEFT", + "localX": 4, + "localZ": 7, + "rotation": 1 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + }, + { + "index": 33, + "roomKey": "league_hall", + "templateRegion": 7535, + "templateZoneX": 5, + "templateZoneZ": 0, + "doorEdges": [ + "WEST", + "EAST", + "SOUTH" + ], + "doorPlacements": [ + { + "edge": "WEST", + "side": "RIGHT", + "localX": 0, + "localZ": 3, + "rotation": 0 + }, + { + "edge": "WEST", + "side": "LEFT", + "localX": 0, + "localZ": 4, + "rotation": 0 + }, + { + "edge": "SOUTH", + "side": "LEFT", + "localX": 3, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "SOUTH", + "side": "RIGHT", + "localX": 4, + "localZ": 0, + "rotation": 3 + }, + { + "edge": "EAST", + "side": "LEFT", + "localX": 7, + "localZ": 3, + "rotation": 2 + }, + { + "edge": "EAST", + "side": "RIGHT", + "localX": 7, + "localZ": 4, + "rotation": 2 + } + ], + "floor": "GROUND" + } + ] +} diff --git a/api/poh/src/main/resources/poh_windows.json b/api/poh/src/main/resources/poh_windows.json new file mode 100644 index 000000000..c40dabdaf --- /dev/null +++ b/api/poh/src/main/resources/poh_windows.json @@ -0,0 +1,872 @@ +{ + "windows": [ + { + "roomKey": "parlour", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "parlour", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "parlour", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "parlour", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "parlour", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "parlour", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "parlour", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "parlour", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "kitchen", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "kitchen", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "kitchen", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "kitchen", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "kitchen", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "kitchen", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "kitchen", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "kitchen", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "dining_room", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "dining_room", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "dining_room", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "dining_room", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "dining_room", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "dining_room", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "bedroom", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "bedroom", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "bedroom", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "bedroom", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "bedroom", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "bedroom", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "bedroom", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "bedroom", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "skill_hall_stairs_up", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "skill_hall_stairs_up", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "skill_hall_stairs_up", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "skill_hall_stairs_up", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "skill_hall_stairs_up", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "skill_hall_stairs_up", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "skill_hall_stairs_up", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "skill_hall_stairs_up", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "skill_hall_stairs_top", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "skill_hall_stairs_top", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "skill_hall_stairs_top", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "skill_hall_stairs_top", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "skill_hall_stairs_top", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "skill_hall_stairs_top", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "skill_hall_stairs_top", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "skill_hall_stairs_top", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "quest_hall_stairs_up", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "quest_hall_stairs_up", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "quest_hall_stairs_up", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "quest_hall_stairs_up", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "quest_hall_stairs_up", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "quest_hall_stairs_up", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "quest_hall_stairs_up", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "quest_hall_stairs_up", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "quest_hall_stairs_top", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "quest_hall_stairs_top", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "quest_hall_stairs_top", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "quest_hall_stairs_top", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "quest_hall_stairs_top", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "quest_hall_stairs_top", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "quest_hall_stairs_top", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "quest_hall_stairs_top", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "workshop", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "workshop", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "workshop", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "workshop", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "workshop", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "workshop", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "workshop", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "workshop", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "study", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "study", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "study", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "study", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "study", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "study", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "study", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "study", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "throne_room", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "throne_room", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "throne_room", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "throne_room", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "throne_room", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "throne_room", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "throne_room", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "throne_room", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "portal_chamber", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "portal_chamber", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "portal_chamber", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "portal_chamber", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "portal_chamber", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "portal_chamber", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "portal_chamber", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "portal_chamber", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "combat_room", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "combat_room", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "combat_room", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "combat_room", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "combat_room", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "combat_room", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "combat_room", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "combat_room", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "games_room", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "games_room", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "games_room", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "games_room", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "games_room", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "games_room", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "games_room", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "games_room", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "costume_room", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "costume_room", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "costume_room", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "costume_room", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "costume_room", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "costume_room", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "portal_nexus", + "localX": 0, + "localZ": 2, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "portal_nexus", + "localX": 0, + "localZ": 5, + "shape": 0, + "rotation": 0 + }, + { + "roomKey": "portal_nexus", + "localX": 2, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "portal_nexus", + "localX": 2, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "portal_nexus", + "localX": 5, + "localZ": 0, + "shape": 0, + "rotation": 3 + }, + { + "roomKey": "portal_nexus", + "localX": 5, + "localZ": 7, + "shape": 0, + "rotation": 1 + }, + { + "roomKey": "portal_nexus", + "localX": 7, + "localZ": 2, + "shape": 0, + "rotation": 2 + }, + { + "roomKey": "portal_nexus", + "localX": 7, + "localZ": 5, + "shape": 0, + "rotation": 2 + } + ] +} diff --git a/content/skills/construction/build.gradle.kts b/content/skills/construction/build.gradle.kts new file mode 100644 index 000000000..a88d498c2 --- /dev/null +++ b/content/skills/construction/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("base-conventions") +} + +dependencies { + implementation(projects.api.attr) + implementation(projects.api.generated) + implementation(projects.api.player) + implementation(projects.api.pluginCommons) + implementation(projects.api.poh) + implementation(projects.api.repo) + implementation(projects.api.registry) + implementation(projects.api.script) + implementation(projects.api.shops) + implementation(projects.engine.events) + implementation(projects.engine.game) + implementation(projects.engine.map) + implementation(projects.engine.plugin) + implementation(projects.engine.routefinder) +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/PohFurniture.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/PohFurniture.kt new file mode 100644 index 000000000..928182184 --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/PohFurniture.kt @@ -0,0 +1,50 @@ +package org.rsmod.content.skills.construction + +import dev.openrune.types.ObjectServerType +import org.rsmod.api.table.PohFurnitureRow + +/** Lookup helpers over `dbtable.poh_furniture`. */ +object PohFurniture { + private val byHotspotLocId: Map> by lazy { + val map = HashMap>() + for (row in PohFurnitureRow.all()) { + for (hotspot in row.hotspotLoc) { + map.getOrPut(hotspot.id) { mutableListOf() } += row + } + } + map.mapValues { entry -> entry.value.sortedWith(compareBy({ it.level }, { it.xp })) } + } + + private val byBuiltLocId: Map> by lazy { + val map = HashMap>() + for (row in PohFurnitureRow.all()) { + for (built in row.builtLoc) { + map.getOrPut(built.id) { mutableListOf() } += row + } + } + map + } + + /** Build options for a hotspot loc, in menu order (level, then xp). */ + fun optionsFor(hotspotLocId: Int): List = + byHotspotLocId[hotspotLocId].orEmpty() + + /** Rows whose built furniture includes [builtLocId]. */ + fun rowsForBuilt(builtLocId: Int): List = byBuiltLocId[builtLocId].orEmpty() + + /** The built loc this row yields on [hotspotLocId], via the aligned hotspot/built lists. */ + fun builtLocFor(row: PohFurnitureRow, hotspotLocId: Int): ObjectServerType? { + val index = row.hotspotLoc.indexOfFirst { it.id == hotspotLocId } + return if (index >= 0) row.builtLoc[index] else null + } + + /** The hotspot loc this row's [builtLocId] reverts to on removal. */ + fun hotspotLocFor(row: PohFurnitureRow, builtLocId: Int): ObjectServerType? { + val index = row.builtLoc.indexOfFirst { it.id == builtLocId } + return if (index >= 0) row.hotspotLoc[index] else null + } + + /** Every distinct built furniture loc across the table. */ + fun allBuiltLocs(): Set = + PohFurnitureRow.all().flatMap { row -> row.builtLoc.map { it.internalName } }.toSet() +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/PohFurnitureIcons.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/PohFurnitureIcons.kt new file mode 100644 index 000000000..011a0bec8 --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/PohFurnitureIcons.kt @@ -0,0 +1,48 @@ +package org.rsmod.content.skills.construction + +import dev.openrune.ServerCacheManager +import org.rsmod.api.table.PohFurnitureRow + +/** + * Resolves the icon obj shown for a build option in `interface.poh_furniture_creation`. + * + * Jagex ships a dedicated, untradeable icon obj for every buildable piece whose name matches the + * menu name exactly and whose category is the POH furniture category 206 (e.g. "Firepit" = obj + * 8216, which also carries the level requirement in its params). Those are authoritative; flatpack + * objs cover flatpackables the icon set misses, and the first build material is the last-resort + * fallback so the menu never renders an empty icon. Name matches outside category 206 are + * deliberately not used - random same-named objs are exactly how icons end up wrong. + */ +object PohFurnitureIcons { + private const val POH_FURNITURE_CATEGORY = 206 + + private val iconsByName: Map by lazy { + val icons = HashMap() + val byExamine = HashMap() + for (item in ServerCacheManager.getItemTypes()) { + if (item.category != POH_FURNITURE_CATEGORY) { + continue + } + val name = item.name.lowercase() + if (name.isNotEmpty()) { + icons.putIfAbsent(name, item.id) + } + // Some icon objs carry the menu name only in their examine (e.g. obj 8258 is named + // "Spiral staircase" but examined "Limestone spiral staircase"). + val examine = item.examine?.lowercase().orEmpty() + if (examine.isNotEmpty()) { + byExamine.putIfAbsent(examine, item.id) + } + } + for ((examine, id) in byExamine) { + icons.putIfAbsent(examine, id) + } + icons + } + + fun iconFor(row: PohFurnitureRow): Int = + iconsByName[row.menuName.lowercase()] + ?: row.flatpack?.id + ?: row.material.firstOrNull()?.t0?.id + ?: -1 +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/PohStairs.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/PohStairs.kt new file mode 100644 index 000000000..41090628e --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/PohStairs.kt @@ -0,0 +1,51 @@ +package org.rsmod.content.skills.construction + +/** + * Staircase connectors that exist as linked pairs across floors. + * + * A staircase is one unit spanning two rooms: hall staircases continue into a free top/bottom half, + * while spiral staircases consume a second set of materials for the matching spiral (wiki: "the + * player needs the resources to build a matching staircase"). Building or removing one end must + * mirror to the other, or the orphaned half clips through the floor between the rooms. + */ +object PohStairs { + /** Hotspot name that hosts staircases in halls and the dungeon stairs room. */ + const val STAIR_SPACE_NAME = "Stair Space" + + /** The connector mirrored into the linked room, with the extra materials it consumes. */ + data class Mirror(val builtLoc: String, val materials: List> = emptyList()) + + val MIRRORS: Map = + mapOf( + "loc.poh_stairs_3" to Mirror("loc.poh_stairstop_3"), + "loc.poh_stairs_4" to Mirror("loc.poh_stairstop_4"), + "loc.poh_stairs_5" to Mirror("loc.poh_stairstop_5"), + "loc.poh_stairstop_3" to Mirror("loc.poh_stairs_3"), + "loc.poh_stairstop_4" to Mirror("loc.poh_stairs_4"), + "loc.poh_stairstop_5" to Mirror("loc.poh_stairs_5"), + "loc.poh_spiralstairs" to + Mirror( + "loc.poh_spiralstairs", + listOf("obj.plank_teak" to 10, "obj.limestonebrick" to 7), + ), + "loc.poh_spiralstairs_2" to + Mirror( + "loc.poh_spiralstairs_2", + listOf("obj.plank_teak" to 10, "obj.marble_block" to 7), + ), + ) + + /** Levels a built connector continues toward: bottoms up, tops down, spirals both ways. */ + fun linkedDirections(builtLoc: String): List = + when (builtLoc) { + "loc.poh_stairs_3", + "loc.poh_stairs_4", + "loc.poh_stairs_5" -> listOf(1) + "loc.poh_stairstop_3", + "loc.poh_stairstop_4", + "loc.poh_stairstop_5" -> listOf(-1) + "loc.poh_spiralstairs", + "loc.poh_spiralstairs_2" -> listOf(-1, 1) + else -> emptyList() + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/AchievementGalleryScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/AchievementGalleryScript.kt new file mode 100644 index 000000000..3313ad57c --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/AchievementGalleryScript.kt @@ -0,0 +1,114 @@ +package org.rsmod.content.skills.construction.features + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.poh.PohManager +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Achievement gallery jewellery box: op2 `Teleport Menu` on the built box opens a destination + * picker whose contents grow with the box tier, per the wiki `Jewellery Box` page: + * - **Basic** (`loc.poh_jewellery_box_1_base`): games necklace + ring of dueling destinations. + * - **Fancy** (`loc.poh_jewellery_box_2_base`): adds skills necklace + combat bracelet. + * - **Ornate** (`loc.poh_jewellery_box_3_base`): adds ring of wealth + amulet of glory. + * + * The `_base` children carry the ops; the placed `poh_jewellery_box_1..3` parents are op-less + * multiloc shells resolved through `varbit` 2308, which defaults to the `_base` child. The + * per-destination child variants (last-destination shortcuts on op1) are not modelled yet. + * + * TODO: Achievement gallery altar-of-the-occult (spellbook swap) and boss lair displays are not + * implemented in this pass. + */ +class AchievementGalleryScript @Inject constructor(private val manager: PohManager) : + PluginScript() { + override fun ScriptContext.startup() { + onOpLoc2(BASIC_BOX) { openJewelleryBox(BASIC_DESTINATIONS) } + onOpLoc2(FANCY_BOX) { openJewelleryBox(FANCY_DESTINATIONS) } + onOpLoc2(ORNATE_BOX) { openJewelleryBox(ORNATE_DESTINATIONS) } + } + + private suspend fun ProtectedAccess.openJewelleryBox( + destinations: List> + ) { + if (!manager.isInOwnHouse(player)) { + mes("You can only use the jewellery box in your own house.") + return + } + val index = menu(MENU_TITLE, hotkeys = true, choices = destinations.map { it.first }) + val destination = destinations.getOrNull(index)?.second ?: return + arriveDelay() + telejump(destination) + mes("You teleport to ${destinations[index].first}.") + } + + private companion object { + const val MENU_TITLE = "Jewellery Box" + + const val BASIC_BOX = "loc.poh_jewellery_box_1_base" + const val FANCY_BOX = "loc.poh_jewellery_box_2_base" + const val ORNATE_BOX = "loc.poh_jewellery_box_3_base" + + /** Games necklace destinations (wiki landing tiles). */ + val GAMES_NECKLACE = + listOf( + "Burthorpe" to CoordGrid(2898, 3553, 0), + "Barbarian Outpost" to CoordGrid(2520, 3571, 0), + "Corporeal Beast" to CoordGrid(2965, 4382, 2), + "Tears of Guthix" to CoordGrid(3244, 9501, 2), + "Wintertodt Camp" to CoordGrid(1623, 3937, 0), + ) + + /** Ring of dueling destinations. */ + val RING_OF_DUELING = + listOf( + "PvP Arena" to CoordGrid(3316, 3235, 0), + "Ferox Enclave" to CoordGrid(3151, 3635, 0), + "Castle Wars" to CoordGrid(2440, 3089, 0), + ) + + /** Skills necklace destinations. */ + val SKILLS_NECKLACE = + listOf( + "Fishing Guild" to PohTeleportDestinations.FISHING_GUILD, + "Mining Guild" to CoordGrid(3046, 9756, 0), + "Crafting Guild" to CoordGrid(2933, 3295, 0), + "Cooking Guild" to CoordGrid(3143, 3442, 0), + "Woodcutting Guild" to CoordGrid(1660, 3505, 0), + "Farming Guild" to CoordGrid(1248, 3718, 0), + ) + + /** Combat bracelet destinations. */ + val COMBAT_BRACELET = + listOf( + "Warriors' Guild" to CoordGrid(2882, 3548, 0), + "Champions' Guild" to CoordGrid(3191, 3363, 0), + "Edgeville Monastery" to CoordGrid(3052, 3488, 0), + "Ranging Guild" to CoordGrid(2655, 3441, 0), + ) + + /** Ring of wealth destinations; the Grand Exchange tile matches silo's spell teleport. */ + val RING_OF_WEALTH = + listOf( + "Miscellania" to CoordGrid(2535, 3861, 0), + "Grand Exchange" to PohTeleportDestinations.GRAND_EXCHANGE, + "Falador Park" to CoordGrid(2995, 3375, 0), + "Dondakan's Rock" to CoordGrid(2831, 10165, 0), + ) + + /** Amulet of glory destinations. */ + val AMULET_OF_GLORY = + listOf( + "Edgeville" to CoordGrid(3087, 3496, 0), + "Karamja" to CoordGrid(2918, 3176, 0), + "Draynor Village" to CoordGrid(3105, 3251, 0), + "Al Kharid" to CoordGrid(3293, 3163, 0), + ) + + val BASIC_DESTINATIONS = GAMES_NECKLACE + RING_OF_DUELING + val FANCY_DESTINATIONS = BASIC_DESTINATIONS + SKILLS_NECKLACE + COMBAT_BRACELET + val ORNATE_DESTINATIONS = FANCY_DESTINATIONS + RING_OF_WEALTH + AMULET_OF_GLORY + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/ChapelScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/ChapelScript.kt new file mode 100644 index 000000000..845030e53 --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/ChapelScript.kt @@ -0,0 +1,208 @@ +package org.rsmod.content.skills.construction.features + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.basePrayerLvl +import org.rsmod.api.player.stat.prayerLvl +import org.rsmod.api.poh.PohManager +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.repo.world.WorldRepository +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLocU +import org.rsmod.api.table.prayer.SkillPrayerRow +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.map.zone.ZoneKey +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Chapel room features: praying at the built altar, offering bones on it, and lighting the incense + * burners. + * + * Bone offerings follow the wiki `Chapel` altar table exactly: each altar tier has a base XP + * multiplier (oak 1.0x through gilded 2.5x) and every lit incense burner in the chapel adds a + * further 0.5x, capping at gilded + 2 burners = 3.5x. XP comes from the same `dbtable.skill_prayer` + * rows the bury and chaos-altar scripts use, so per-bone XP can never drift between the paths. This + * script owns every POH altar skin and tier, including the gilded skins previously bound at a flat + * 3.5x by the prayer module's `GildedAltarEvents` (which now handles only the chaos altar). + * + * The altar built in the chapel is `loc.poh_altar_saradomin_`; the zamorak/guthix/gnomechild + * skins are registered too so a future icon-driven altar swap needs no changes here. + * + * Burners are the tier 5-7 lamp builds (`loc.poh_torch_5..7`); lighting one requires a tinderbox + * and consumes a clean marrentill per the wiki, swapping to the `_lit` variant for a limited burn + * time. The lit variant's `Re-light` op refuels it for another full duration. + */ +class ChapelScript +@Inject +constructor( + private val manager: PohManager, + private val locRepo: LocRepository, + private val worldRepo: WorldRepository, +) : PluginScript() { + private val litBurnerIds by lazy { BURNERS.map { it.lit.asRSCM(RSCMType.LOC) }.toSet() } + + override fun ScriptContext.startup() { + for (altar in ALTARS) { + onOpLoc1(altar.loc) { prayAtAltar() } + } + + val bones = SkillPrayerRow.all().filterNot { it.ashes } + for (altar in ALTARS) { + for (row in bones) { + onOpLocU(altar.loc, row.item.internalName) { offerBones(it.vis, row, altar.tier) } + } + } + + for (burner in BURNERS) { + onOpLoc1(burner.unlit) { lightBurner(it.loc, burner.lit, relight = false) } + onOpLocU(burner.unlit, TINDERBOX) { lightBurner(it.vis, burner.lit, relight = false) } + onOpLoc1(burner.lit) { lightBurner(it.loc, burner.lit, relight = true) } + } + } + + private suspend fun ProtectedAccess.prayAtAltar() { + arriveDelay() + anim(PRAY_ANIM) + delay(2) + + val missing = player.basePrayerLvl - player.prayerLvl + if (missing > 0) { + statAdd(PRAYER_STAT, constant = missing, percent = 0) + mes("You recharge your Prayer points.") + } else { + mes("You already have full prayer points.") + } + } + + /** + * Offers [row]'s bone repeatedly until the player runs out, moves away, or interrupts the + * action. One bone is consumed every [OFFER_INTERVAL] ticks, matching the gilded-altar cadence. + */ + private suspend fun ProtectedAccess.offerBones( + altar: BoundLocInfo, + row: SkillPrayerRow, + tier: Int, + ) { + if (!manager.isInOwnHouse(player)) { + mes("You can only make offerings at an altar in your own house.") + return + } + arriveDelay() + + val bone = row.item.internalName + while (inv.count(bone) > 0 && isWithinDistance(altar, 1)) { + anim(SACRIFICE_ANIM) + spotanimMap(worldRepo, SACRIFICE_SPOTANIM, altar.coords) + + val result = invDel(inv, bone, 1) + if (result.failure) { + return + } + + val litBurners = countLitBurners(altar) + val multiplier = TIER_MULTIPLIERS.getValue(tier) + BURNER_BONUS * litBurners + statAdvance(PRAYER_STAT, row.exp * multiplier) + mes(offeringMessage(litBurners)) + + delay(OFFER_INTERVAL) + } + } + + /** Counts lit burner locs in the altar's room zone; a chapel holds at most two burners. */ + private fun countLitBurners(altar: BoundLocInfo): Int { + val zone = ZoneKey.from(altar.coords) + return locRepo.findAll(zone).count { it.id in litBurnerIds }.coerceAtMost(MAX_BURNERS) + } + + private fun offeringMessage(litBurners: Int): String = + when (litBurners) { + 0 -> "The gods accept your offering." + 1 -> "The gods are pleased with your offering." + else -> "The gods are very pleased with your offering." + } + + private suspend fun ProtectedAccess.lightBurner( + burner: BoundLocInfo, + litLoc: String, + relight: Boolean, + ) { + if (!manager.isInOwnHouse(player)) { + mes("You can only do that in your own house.") + return + } + if (invTotal(inv, TINDERBOX) < 1) { + mes("You need a tinderbox to light the burner.") + return + } + if (invTotal(inv, MARRENTILL) < 1) { + mes("You need a clean marrentill to burn in the incense burner.") + return + } + arriveDelay() + + val result = invDel(inv, MARRENTILL, 1) + if (result.failure) { + return + } + anim(LIGHT_ANIM) + delay(1) + + locRepo.change(burner, litLoc, burnDuration()) + mes(if (relight) "You refuel the incense burner." else "You light the incense burner.") + } + + private fun burnDuration(): Int = (BURN_TICKS_MIN..BURN_TICKS_MAX).random() + + private data class ChapelAltar(val loc: String, val tier: Int) + + private data class ChapelBurner(val unlit: String, val lit: String) + + private companion object { + const val PRAYER_STAT = "stat.prayer" + const val PRAY_ANIM = "seq.human_pray" + const val SACRIFICE_ANIM = "seq.human_bone_sacrifice" + const val SACRIFICE_SPOTANIM = "spotanim.poh_bone_sacrifice" + const val LIGHT_ANIM = "seq.human_createfire" + + const val TINDERBOX = "obj.tinderbox" + + /** Clean marrentill; the cache gameval spells it `marentill`. */ + const val MARRENTILL = "obj.marentill" + + /** Ticks between bone offerings, matching the prayer module's sacrifice queue cadence. */ + const val OFFER_INTERVAL = 4 + + /** Extra XP multiplier granted per lit incense burner (wiki: +50% each). */ + const val BURNER_BONUS = 0.5 + + const val MAX_BURNERS = 2 + + /** A burner stays lit for roughly two minutes plus a random grace period, as on OSRS. */ + const val BURN_TICKS_MIN = 200 + const val BURN_TICKS_MAX = 250 + + /** Base bone-offering XP multiplier per altar tier (wiki `Chapel` altar table). */ + val TIER_MULTIPLIERS = + mapOf(1 to 1.0, 2 to 1.1, 3 to 1.25, 4 to 1.5, 5 to 1.75, 6 to 2.0, 7 to 2.5) + + /** Every altar skin x tier; tier 1 = oak ... tier 7 = gilded. */ + val ALTARS = buildList { + for (god in listOf("saradomin", "zamorak", "guthix", "gnomechild")) { + for (tier in 1..7) { + add(ChapelAltar("loc.poh_altar_${god}_$tier", tier)) + } + } + } + + /** Lamp-space tiers 5-7 are the oak/mahogany/marble incense burners. */ + val BURNERS = + listOf( + ChapelBurner("loc.poh_torch_5", "loc.poh_torch_5_lit"), + ChapelBurner("loc.poh_torch_6", "loc.poh_torch_6_lit"), + ChapelBurner("loc.poh_torch_7", "loc.poh_torch_7_lit"), + ) + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/CostumeRoomScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/CostumeRoomScript.kt new file mode 100644 index 000000000..bfd4f22db --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/CostumeRoomScript.kt @@ -0,0 +1,417 @@ +package org.rsmod.content.skills.construction.features + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.poh.PohManager +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Costume room storage: armour case, cape rack, magic wardrobe, toy box, treasure chest and fancy + * dress box. + * + * Item storage is backed by the real cache inventories declared in + * `.data/raw-cache/server/inv.toml` (`inv.poh_costume_room_armour_inv`, + * `inv.poh_costume_room_capes_inv`, `inv.poh_costume_room_magic_wardrobe_inv`, + * `inv.poh_costume_room_holiday_items_inv`, `inv.poh_costume_room_ame_inv` and + * `inv.poh_costume_room_treasure_trail_0..3_inv`), all `Perm`-scoped so contents persist through + * the standard inventory persistence path. + * + * The real `interface.poh_costumes` / `interface.poh_costumes_side` pair is driven by clientscripts + * whose wire format was never exercised in the available RSProx captures, so this script falls back + * to a menu-driven deposit/withdraw dialogue against the same inventories - storage correctness + * over pixel parity. Wiring the real interface is a TODO once a capture of the costume room UI + * exists. + * + * Storable item lists are a curated starter subset of the OSRS wiki "Costume room" storable lists + * (wiki pages `Armour case space`, `Cape rack space`, `Magic wardrobe space`, `Toy box space`, + * `Treasure chest space` and `Fancy dress box space`); the fancy dress box list is complete. Every + * obj name below resolves against the rev240 gamevals. Treasure chest deposits are tier-gated by + * the built chest (oak: beginner/easy, teak: +medium, mahogany: all), matching the wiki storage + * limits; withdrawals are never tier-gated so a downgraded chest can always be emptied. Skillcape + * count limits per cape rack tier are not enforced (starter set only carries two skillcapes). + */ +class CostumeRoomScript @Inject constructor(private val manager: PohManager) : PluginScript() { + override fun ScriptContext.startup() { + for (storage in STORAGE_UNITS) { + for (loc in storage.builtLocs) { + onOpLoc1(loc) { onStorageOpen(storage) } + } + } + for ((loc, tierCount) in TREASURE_CHESTS) { + onOpLoc1(loc) { onTreasureChestOpen(tierCount) } + } + } + + private suspend fun ProtectedAccess.onStorageOpen(storage: StorageUnit) { + if (!verifyOwnHouse()) { + return + } + val deposit = + choice2("Deposit items.", true, "Withdraw items.", false, title = storage.displayName) + if (deposit) { + depositMenu(storage.displayName, listOf(storage.inv to storage.storables)) + } else { + withdrawMenu(storage.displayName, listOf(storage.inv)) + } + } + + private suspend fun ProtectedAccess.onTreasureChestOpen(tierCount: Int) { + if (!verifyOwnHouse()) { + return + } + val deposit = + choice2("Deposit items.", true, "Withdraw items.", false, title = "Treasure chest") + if (deposit) { + val allowed = TREASURE_TIERS.take(tierCount).map { it.inv to it.storables } + depositMenu("Treasure chest", allowed) + } else { + withdrawMenu("Treasure chest", TREASURE_TIERS.map { it.inv }) + } + } + + /** Storage locs only exist in a house region; owner-only access mirrors live behaviour. */ + private fun ProtectedAccess.verifyOwnHouse(): Boolean = manager.isInOwnHouse(player) + + /** + * Lists every storable item the player carries across [sections] (an `inv name -> storables` + * pair per treasure tier; single-section for the other storage types) and deposits the full + * carried count of the chosen item. + */ + private suspend fun ProtectedAccess.depositMenu( + displayName: String, + sections: List>>, + ) { + val candidates = buildList { + for ((invName, storables) in sections) { + for (obj in storables) { + if (invTotal(inv, obj) > 0) { + add(invName to obj) + } + } + } + } + if (candidates.isEmpty()) { + mesbox("You don't have anything that can be stored in the $displayName.") + return + } + val labels = candidates.map { (_, obj) -> itemName(obj) } + val index = menu("Deposit which item?", hotkeys = false, labels) + val (invName, obj) = candidates.getOrNull(index) ?: return + val count = invTotal(inv, obj) + if (invDel(inv, obj, count).failure) { + return + } + val storage = inv(invName) + if (invAdd(storage, obj, count).failure) { + invAdd(inv, obj, count) + mes("The $displayName is full.") + return + } + mes("You store the ${itemName(obj)} in the $displayName.") + } + + /** Lists stored items across [invNames] and returns the full stored count of the choice. */ + private suspend fun ProtectedAccess.withdrawMenu(displayName: String, invNames: List) { + val stored = buildList { + for (invName in invNames) { + val storage = inv(invName) + for (slot in storage) { + if (slot != null && slot.count > 0) { + val obj = RSCM.getReverseMapping(RSCMType.OBJ, slot.id) + add(Triple(storage, obj, slot.count)) + } + } + } + } + if (stored.isEmpty()) { + mesbox("The $displayName is empty.") + return + } + val labels = + stored.map { (_, obj, count) -> + if (count > 1) "${itemName(obj)} x$count" else itemName(obj) + } + val index = menu("Withdraw which item?", hotkeys = false, labels) + val (storage, obj, count) = stored.getOrNull(index) ?: return + if (invAdd(inv, obj, count).failure) { + mes("You don't have enough inventory space to withdraw that.") + return + } + invDel(storage, obj, count) + mes("You take the ${itemName(obj)} out of the $displayName.") + } + + private fun itemName(obj: String): String { + val item = ServerCacheManager.getItem(obj.asRSCM(RSCMType.OBJ)) + return item?.name?.takeIf { it.isNotBlank() } ?: obj + } + + private class StorageUnit( + val displayName: String, + val inv: String, + val builtLocs: List, + val storables: List, + ) + + private class TreasureTier(val inv: String, val storables: List) + + private companion object { + /** + * Non-chest storage units. Built loc names are the closed variants placed by the furniture + * build flow; the `_open_` loc variants are never spawned by this server, so only the + * closed locs carry handlers (op1 is `Open` on cases/boxes and `Search` on the cape rack - + * both open the storage dialogue directly). + */ + val STORAGE_UNITS = + listOf( + StorageUnit( + displayName = "armour case", + inv = "inv.poh_costume_room_armour_inv", + builtLocs = + listOf( + "loc.poh_cos_room_armour_case_oak", + "loc.poh_cos_room_armour_case_teak", + "loc.poh_cos_room_armour_case_mahogany", + ), + // Wiki "Armour case space": Angler outfit, Rogue equipment, Lumberjack outfit. + storables = + listOf( + "obj.trawler_reward_hat", + "obj.trawler_reward_top", + "obj.trawler_reward_legs", + "obj.trawler_reward_boots", + "obj.roguesden_helm", + "obj.roguesden_body", + "obj.roguesden_legs", + "obj.roguesden_gloves", + "obj.roguesden_boots", + "obj.ramble_lumberjack_hat", + "obj.ramble_lumberjack_top", + "obj.ramble_lumberjack_legs", + "obj.ramble_lumberjack_boots", + ), + ), + StorageUnit( + displayName = "cape rack", + inv = "inv.poh_costume_room_capes_inv", + builtLocs = + listOf( + "loc.poh_cos_room_cape_rack_oak", + "loc.poh_cos_room_cape_rack_teak", + "loc.poh_cos_room_cape_rack_mahogany", + "loc.poh_cos_room_cape_rack_mahogany_gilded", + "loc.poh_cos_room_cape_rack_marble", + "loc.poh_cos_room_cape_rack_magic_stone", + ), + // Wiki "Cape rack space": fire/infernal/obsidian capes, cape of legends and + // hood+cape+trimmed skillcape sets (attack and construction as starters). + storables = + listOf( + "obj.tzhaar_cape_fire", + "obj.infernal_cape", + "obj.tzhaar_cape_obsidian", + "obj.cape_of_legends", + "obj.skillcape_attack", + "obj.skillcape_attack_trimmed", + "obj.skillcape_attack_hood", + "obj.skillcape_construction", + "obj.skillcape_construction_trimmed", + "obj.skillcape_construction_hood", + ), + ), + StorageUnit( + displayName = "magic wardrobe", + inv = "inv.poh_costume_room_magic_wardrobe_inv", + builtLocs = + listOf( + "loc.poh_cos_room_magic_wardrobe_oak", + "loc.poh_cos_room_magic_wardrobe_carved_oak", + "loc.poh_cos_room_magic_wardrobe_teak", + "loc.poh_cos_room_magic_wardrobe_carved_teak", + "loc.poh_cos_room_magic_wardrobe_mahogany", + "loc.poh_cos_room_magic_wardrobe_mahogany_gilded", + "loc.poh_cos_room_magic_wardrobe_marble", + ), + // Wiki "Magic wardrobe space": infinity, splitbark, skeletal, dagon'hai and + // elder chaos robes. + storables = + listOf( + "obj.magictraining_infinityhat", + "obj.magictraining_infinitytop", + "obj.magictraining_infinitybottom", + "obj.magictraining_infinitygloves", + "obj.magictraining_infinityboots", + "obj.splitbark_helm", + "obj.splitbark_body", + "obj.splitbark_legs", + "obj.splitbark_gauntlets", + "obj.splitbark_greaves", + "obj.dagganoth_mage_helm", + "obj.dagganoth_mage_body", + "obj.dagganoth_mage_legs", + "obj.dagganoth_mage_gloves", + "obj.dagganoth_mage_feet", + "obj.dagonhai_hat", + "obj.dagonhai_robe_top", + "obj.dagonhai_robe_bottom", + "obj.elderchaos_hood", + "obj.elderchaos_top", + "obj.elderchaos_bottom", + ), + ), + StorageUnit( + displayName = "toy box", + inv = "inv.poh_costume_room_holiday_items_inv", + builtLocs = + listOf( + "loc.poh_cos_room_toy_box_oak", + "loc.poh_cos_room_toy_box_teak", + "loc.poh_cos_room_toy_box_mahogany", + ), + // Wiki "Toy box space": untradeable holiday rewards. + storables = + listOf( + "obj.xmas_yoyo", + "obj.rubber_chicken", + "obj.bunnyears", + "obj.scythe", + "obj.easter06_ring_of_egg", + "obj.hw06_pumpkin_head", + "obj.hw06_spooky_head", + "obj.hw06_spooky_body", + "obj.hw06_spooky_legs", + "obj.hw06_spooky_gloves", + "obj.hw06_spooky_boots", + "obj.gub_reindeer_hat", + "obj.easter07_chicken_head", + "obj.easter07_chicken_wings", + "obj.easter07_chicken_legs", + "obj.easter07_chicken_feet", + "obj.hw07_grim_hood", + "obj.win05_marionette_complete_red_alive", + "obj.win05_marionette_complete_blue_alive", + "obj.win05_marionette_complete_green_alive", + ), + ), + StorageUnit( + displayName = "fancy dress box", + inv = "inv.poh_costume_room_ame_inv", + builtLocs = + listOf( + "loc.poh_cos_room_fancy_dress_box_oak", + "loc.poh_cos_room_fancy_dress_box_teak", + "loc.poh_cos_room_fancy_dress_box_mahogany", + ), + // Wiki "Fancy dress box space" - complete random event costume list: + // beekeeper, camo (drill demon), frog/royal frog, lederhosen, mime, + // gravedigger zombie, plus the stale baguette. Shade robes are excluded: + // no dedicated random-event shade robe obj resolves in the rev240 gamevals. + // TODO: map the fancy dress box shade robes once their obj ids are known. + storables = + listOf( + "obj.beekeeper_hat", + "obj.beekeeper_top", + "obj.beekeeper_legs", + "obj.beekeeper_gloves", + "obj.beekeeper_boots", + "obj.drill_helm", + "obj.drill_top", + "obj.drill_bottoms", + "obj.macro_frog_mask", + "obj.macro_prince_torso", + "obj.macro_prince_legs", + "obj.macro_princess_torso", + "obj.macro_princess_legs", + "obj.laderhosen_hat", + "obj.laderhosen_top", + "obj.laderhosen_legs", + "obj.macro_mime_mask", + "obj.macro_mime_top", + "obj.macro_mime_legs", + "obj.macro_mime_gloves", + "obj.macro_mime_boots", + "obj.macro_digger_mask", + "obj.macro_digger_shirt", + "obj.macro_digger_legs", + "obj.macro_digger_gloves", + "obj.macro_digger_boots", + "obj.stale_baguette", + ), + ), + ) + + /** + * Treasure trail tiers in deposit-gate order; index-aligned with the tier inventories. + * Elite and master rewards are not yet mapped to a tier inventory. + * + * TODO: extend with `inv.poh_costume_room_treasure_trail_1a/2a/3a_inv` for elite/master + * rewards once their live tier mapping is confirmed. + */ + val TREASURE_TIERS = + listOf( + // Beginner. + TreasureTier( + inv = "inv.poh_costume_room_treasure_trail_0_inv", + storables = + listOf( + "obj.mole_slippers", + "obj.frog_slippers", + "obj.bear_slippers", + "obj.demon_slippers", + "obj.jester_cape", + "obj.shoulder_parrot", + "obj.sandwich_lady_hat", + "obj.sandwich_lady_top", + "obj.sandwich_lady_bottom", + ), + ), + // Easy. + TreasureTier( + inv = "inv.poh_costume_room_treasure_trail_1_inv", + storables = + listOf( + "obj.berret_black", + "obj.berret_blue", + "obj.berret_white", + "obj.headband_red", + "obj.headband_black", + "obj.headband_brown", + "obj.highwayman_mask", + "obj.trail_amulet_of_magic", + ), + ), + // Medium. + TreasureTier( + inv = "inv.poh_costume_room_treasure_trail_2_inv", + storables = listOf("obj.boots_ranger", "obj.boots_wizard", "obj.holy_sandals"), + ), + // Hard. + TreasureTier( + inv = "inv.poh_costume_room_treasure_trail_3_inv", + storables = + listOf( + "obj.robinhoodhat", + "obj.trail_ranger_coif", + "obj.trail_ranger_torso", + "obj.trail_ranger_legs", + "obj.trail_ranger_vambraces", + "obj.trail_armadyl_chaps", + ), + ), + ) + + /** Built chest loc -> number of [TREASURE_TIERS] it accepts deposits for. */ + val TREASURE_CHESTS = + listOf( + "loc.poh_cos_room_tresure_chest_oak" to 2, + "loc.poh_cos_room_tresure_chest_teak" to 3, + "loc.poh_cos_room_tresure_chest_mahogany" to 4, + ) + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/DungeonScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/DungeonScript.kt new file mode 100644 index 000000000..18c6a6786 --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/DungeonScript.kt @@ -0,0 +1,70 @@ +package org.rsmod.content.skills.construction.features + +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Maps a built guard/guardian furniture loc to the npc that stands on it. All pairs are verified + * gamevals: built locs 13366-13378 mirror npc ids 130-142 name-for-name. + * + * Dungeon and oubliette "Guard space" monsters: skeleton, guard dog, hobgoblin, troll, huge (giant) + * spider, hellhound, baby red dragon and steel dragon. Treasure-room "Monster space" guardians: + * demon, kalphite soldier, TzHaar (Tok-Xil), dagannoth and steel dragon. The oubliette ogre has an + * npc (`npc.poh_ogre`, 136) but no `loc.poh_ogre` built loc in the cache, so it is intentionally + * absent. + */ +internal object PohGuards { + val NPC_BY_BUILT_LOC: Map = + mapOf( + "loc.poh_skeleton" to "npc.poh_skeleton", + "loc.poh_guarddog" to "npc.poh_guarddog", + "loc.poh_hobgoblin" to "npc.poh_hobgoblin", + "loc.poh_troll" to "npc.poh_troll", + "loc.poh_giantspider" to "npc.poh_giantspider", + "loc.poh_hellhound" to "npc.poh_hellhound", + "loc.poh_babyreddragon" to "npc.poh_babyreddragon", + "loc.poh_oub_monster1" to "npc.poh_oub_monster1", + "loc.poh_kalphite_soldier" to "npc.poh_kalphite_soldier", + "loc.poh_steel_dragon" to "npc.poh_steel_dragon", + "loc.poh_dagganoth" to "npc.poh_dagganoth", + "loc.poh_tok_xil" to "npc.poh_tok_xil", + ) +} + +/** + * Dungeon, oubliette and treasure-room content. + * + * Monster spawning itself is owned by [PohNpcSpawner], which reads [PohGuards.NPC_BY_BUILT_LOC] + * against the house's built furniture on every `PohHouseEnteredEvent`. This script binds only the + * treasure-room chest interaction; dungeon stairs, trapdoors and oubliette ladders belong to the + * stairs script. + */ +class DungeonScript : PluginScript() { + override fun ScriptContext.startup() { + for (chest in TREASURE_CHESTS) { + onOpLoc1(chest) { onTreasureChest() } + } + } + + /** + * TODO: live treasure chests swap to their opened variant and hold loot for house guests + * (guests are out of scope for this pass), so for now the chest is a flavour message only. + */ + private fun ProtectedAccess.onTreasureChest() { + mes("You open the chest, but find nothing interesting inside.") + } + + private companion object { + /** The closed treasure-chest built locs (op1 = Open); ids 13283-13291, verified. */ + val TREASURE_CHESTS = + listOf( + "loc.poh_treasure_woodencrate", + "loc.poh_treasure_oak_chest", + "loc.poh_treasure_teak_chest", + "loc.poh_treasure_mag_chest", + "loc.poh_treasure_magic_chest", + ) + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/MenagerieScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/MenagerieScript.kt new file mode 100644 index 000000000..26be5934a --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/MenagerieScript.kt @@ -0,0 +1,454 @@ +package org.rsmod.content.skills.construction.features + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.poh.PohManager +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Menagerie pet storage on the built pet house (and pet list) locs. + * + * Pet storage state lives in the real menagerie varbits, so stored pets persist through the + * standard varp persistence path and stay compatible with the client's own menagerie clientscripts: + * - One 1-bit *stored* flag per pet: `varbit.poh_menagerie_` for the classic pets and + * `varbit.pet_menagerie_` for every pet released after the naming switch (verified per entry + * below; all resolve as 1-bit varbits in the rev240 cache). + * - Pets with cosmetic variants additionally carry a `varbit.poh_menagerie_multiform_*` *form* + * varbit whose value is the 0-based index of the stored form. Every form list below fits its + * varbit's verified bit width. + * + * The follower/pet system is not implemented in silo yet (see `EquipmentTabScript`'s "Call + * follower" TODO), so storage works against the pet *items* the player carries: storing a pet + * consumes its inventory item and sets the flag (and form) varbits; withdrawing gives the item back + * and clears the flag. TODO: once followers exist, also allow storing the currently-following pet. + * + * `interface.poh_menagerie` (211) / `interface.poh_petlist` (210) are clientscript-driven and no + * capture of their wire format exists, so interaction is a menu dialogue instead. + * + * TODO: open the real interface once a capture pins its open/refresh protocol; the varbits this + * script writes are the same ones those interfaces read. + */ +class MenagerieScript @Inject constructor(private val manager: PohManager) : PluginScript() { + override fun ScriptContext.startup() { + for (loc in PET_STORAGE_LOCS) { + onOpLoc1(loc) { onPetStorage() } + } + } + + private suspend fun ProtectedAccess.onPetStorage() { + if (!manager.isInOwnHouse(player)) { + return + } + val store = choice2("Store a pet.", true, "Withdraw a pet.", false, title = "Pet house") + if (store) { + storeMenu() + } else { + withdrawMenu() + } + } + + /** Lists carried pet items whose pet is not already stored; stores the chosen one. */ + private suspend fun ProtectedAccess.storeMenu() { + val candidates = buildList { + for (pet in STORABLE_PETS) { + if (vars[pet.storedVarbit] != 0) { + continue + } + for ((formIndex, form) in pet.forms.withIndex()) { + if (invTotal(inv, form) > 0) { + add(Triple(pet, formIndex, form)) + break + } + } + } + } + if (candidates.isEmpty()) { + mesbox("You don't have any pets that can be stored here.") + return + } + val labels = candidates.map { (_, _, form) -> itemName(form) } + val index = menu("Store which pet?", hotkeys = false, labels) + val (pet, formIndex, form) = candidates.getOrNull(index) ?: return + if (invDel(inv, form, count = 1).failure) { + return + } + vars[pet.storedVarbit] = 1 + pet.formVarbit?.let { vars[it] = formIndex } + mes("You put the ${itemName(form)} into the pet house.") + } + + /** Lists stored pets (flag varbit set) and gives the stored form's item back. */ + private suspend fun ProtectedAccess.withdrawMenu() { + val stored = buildList { + for (pet in STORABLE_PETS) { + if (vars[pet.storedVarbit] != 1) { + continue + } + val formIndex = pet.formVarbit?.let { vars[it] } ?: 0 + add(pet to pet.forms[formIndex.coerceIn(0, pet.forms.size - 1)]) + } + } + if (stored.isEmpty()) { + mesbox("You don't have any pets stored here.") + return + } + val labels = stored.map { (_, form) -> itemName(form) } + val index = menu("Withdraw which pet?", hotkeys = false, labels) + val (pet, form) = stored.getOrNull(index) ?: return + if (invAdd(inv, form, count = 1).failure) { + mes("You don't have enough inventory space to withdraw that.") + return + } + vars[pet.storedVarbit] = 0 + pet.formVarbit?.let { vars[it] = 0 } + mes("You take the ${itemName(form)} out of the pet house.") + } + + private fun itemName(obj: String): String { + val item = ServerCacheManager.getItem(obj.asRSCM(RSCMType.OBJ)) + return item?.name?.takeIf { it.isNotBlank() } ?: obj + } + + /** + * A storable pet: its 1-bit stored flag, an optional form varbit, and the pet item obj per form + * (index-aligned with the form varbit's value). + */ + private class StorablePet( + val storedVarbit: String, + val formVarbit: String?, + val forms: List, + ) + + private companion object { + /** + * Built pet house locs (op1 `View`) plus the pet list. Every name resolves in the rev240 + * loc gamevals (26297-26299, 26830-26832 and 26868). + */ + val PET_STORAGE_LOCS = + listOf( + "loc.poh_menagerie_pethouse_1", + "loc.poh_menagerie_pethouse_2", + "loc.poh_menagerie_pethouse_3", + "loc.poh_menagerie_pethouse_4", + "loc.poh_menagerie_pethouse_5", + "loc.poh_menagerie_pethouse_6", + "loc.poh_menagerie_petlist_1", + ) + + private fun pet(stored: String, vararg forms: String) = + StorablePet(stored, formVarbit = null, forms.toList()) + + private fun multiformPet(stored: String, form: String, vararg forms: String) = + StorablePet(stored, form, forms.toList()) + + /** + * The pet item obj <-> varbit table, built from the rev240 `varbit.poh_menagerie_*` and + * `varbit.pet_menagerie_*` gamevals. Every varbit and obj name below was verified against + * the merged gameval mappings and the SERVER cache varbit definitions. + * + * Not covered: + * - `varbit.pet_menagerie_skillpetsailing`: the sailing skill pet item obj is not yet + * verifiable in this revision's gamevals. TODO: map once the item ships. + * - `varbit.poh_menagerie_closed`, `poh_menagerie2_overview`, `poh_menagerie3_overview`: + * interface state, not pets. + */ + val STORABLE_PETS = + listOf( + // Classic single-form boss pets (poh_menagerie_* stored flags). + pet("varbit.poh_menagerie_chaoselepet", "obj.chaoselepet"), + pet("varbit.poh_menagerie_supremepet", "obj.supremepet"), + pet("varbit.poh_menagerie_primepet", "obj.primepet"), + pet("varbit.poh_menagerie_rexpet", "obj.rexpet"), + pet("varbit.poh_menagerie_penancepet", "obj.penancepet"), + pet("varbit.poh_menagerie_armadylpet", "obj.armadylpet"), + pet("varbit.poh_menagerie_bandospet", "obj.bandospet"), + pet("varbit.poh_menagerie_saradominpet", "obj.saradominpet"), + pet("varbit.poh_menagerie_zamorakpet", "obj.zamorakpet"), + pet("varbit.poh_menagerie_kbdpet", "obj.kbdpet"), + pet("varbit.poh_menagerie_krakenpet", "obj.krakenpet"), + pet("varbit.poh_menagerie_chompypet", "obj.chompybird_pet"), + pet("varbit.poh_menagerie_scorpiapet", "obj.scorpia_pet"), + pet("varbit.poh_menagerie_hellpet", "obj.hell_pet"), + pet("varbit.poh_menagerie_zalcano", "obj.zalcanopet"), + // Classic pets with a multiform twin: stored flag + 0-based form varbit. + multiformPet( + "varbit.poh_menagerie_molepet", + "varbit.poh_menagerie_multiform_molepet", + "obj.molepet", + "obj.molepet_naked", + ), + multiformPet( + "varbit.poh_menagerie_kqpet", + "varbit.poh_menagerie_multiform_kqpet", + "obj.kqpet_walking", + "obj.kqpet_flying", + ), + multiformPet( + "varbit.poh_menagerie_smokepet", + "varbit.poh_menagerie_multiform_smokepet", + "obj.smokepet", + "obj.smokepet_old", + ), + multiformPet( + "varbit.poh_menagerie_snakepet", + "varbit.poh_menagerie_multiform_snakepet", + "obj.snakepet", + "obj.snakepet_orange", + "obj.snakepet_blue", + ), + multiformPet( + "varbit.poh_menagerie_venenatispet", + "varbit.poh_menagerie_multiform_venenatispet", + "obj.venenatis_pet", + "obj.venenatis_pet_legacy", + ), + multiformPet( + "varbit.poh_menagerie_callistopet", + "varbit.poh_menagerie_multiform_callistopet", + "obj.callisto_pet", + "obj.callisto_pet_legacy", + ), + multiformPet( + "varbit.poh_menagerie_vetionpet", + "varbit.poh_menagerie_multiform_vetionpet", + "obj.vetion_pet", + "obj.vetion_pet2", + "obj.vetion_pet_legacy", + "obj.vetion_pet2_legacy", + ), + // The dark core / corporeal critter share one pet slot: `corepet` is the stored + // flag, the `corppet` multiform varbit picks the displayed form. + multiformPet( + "varbit.poh_menagerie_corepet", + "varbit.poh_menagerie_multiform_corppet", + "obj.corepet", + "obj.corppet", + ), + multiformPet( + "varbit.poh_menagerie_jadpet", + "varbit.poh_menagerie_multiform_jadpet", + "obj.jad_pet", + "obj.jad_pet_inferno", + ), + multiformPet( + "varbit.poh_menagerie_sarachnispet", + "varbit.poh_menagerie_multiform_sarachnispet", + "obj.sarachnispet", + "obj.sarachnispet_orange", + "obj.sarachnispet_blue", + ), + // Newer boss pets (pet_menagerie_* stored flags). + multiformPet( + "varbit.pet_menagerie_gargboss", + "varbit.poh_menagerie_multiform_gargboss", + "obj.dawnpet", + "obj.duskpet", + ), + multiformPet( + "varbit.pet_menagerie_inferno", + "varbit.poh_menagerie_multiform_infernopet", + "obj.infernopet", + "obj.infernopet_zuk", + ), + pet("varbit.pet_menagerie_olm", "obj.olmpet"), + multiformPet( + "varbit.pet_menagerie_hydra", + "varbit.poh_menagerie_multiform_hydrapet", + "obj.hydrapet", + "obj.hydrapet_electric", + "obj.hydrapet_fire", + "obj.hydrapet_extinguished", + ), + multiformPet( + "varbit.pet_menagerie_gauntlet", + "varbit.poh_menagerie_multiform_gauntletpet", + "obj.gauntletpet", + "obj.gauntletpet_corrupt", + ), + multiformPet( + "varbit.pet_menagerie_phoenix", + "varbit.poh_menagerie_mutliform_phoenixpet", + "obj.phoenixpet", + "obj.phoenixpet_green", + "obj.phoenixpet_blue", + "obj.phoenixpet_white", + "obj.phoenixpet_purple", + ), + multiformPet( + "varbit.pet_menagerie_soulwars", + "varbit.poh_menagerie_multiform_soulwarspet", + "obj.soulwarspet_red", + "obj.soulwarspet_blue", + ), + multiformPet( + "varbit.pet_menagerie_nightmare", + "varbit.poh_menagerie_multiform_nightmarepet", + "obj.nightmarepet", + "obj.nightmarepet_parasite", + ), + pet("varbit.pet_menagerie_verzik", "obj.verzikpet"), + multiformPet( + "varbit.pet_menagerie_wardens", + "varbit.poh_menagerie_multiform_wardenpet", + "obj.wardenpet_tumeken", + "obj.wardenpet_elidinis", + "obj.wardenpet_akkha", + "obj.wardenpet_baba", + "obj.wardenpet_kephri", + "obj.wardenpet_zebak", + ), + multiformPet( + "varbit.pet_menagerie_muspah", + "varbit.poh_menagerie_multiform_muspahpet", + "obj.muspahpet", + "obj.muspahpet_melee", + "obj.muspahpet_shielded", + ), + multiformPet( + "varbit.pet_menagerie_araxxor", + "varbit.poh_menagerie_multiform_araxxorpet", + "obj.araxxorpet", + "obj.araxxorpet_cute", + ), + // TODO: only Branda's item (`obj.rtbrandapet`) resolves for the Royal Titans pet; + // add Eldric's form if a matching obj exists in a later revision. + multiformPet( + "varbit.pet_menagerie_royaltitans", + "varbit.poh_menagerie_multiform_royaltitanpet", + "obj.rtbrandapet", + ), + multiformPet( + "varbit.pet_menagerie_gryphon", + "varbit.poh_menagerie_multiform_gryphonbosspet", + "obj.gryphonbosspet", + "obj.gryphonbosspet_adult", + ), + pet("varbit.pet_menagerie_abyssalsirepet", "obj.abyssalsire_pet"), + pet("varbit.pet_menagerie_bloodhound", "obj.bloodhound_pet"), + pet("varbit.pet_menagerie_herbiboar", "obj.herbiboarpet"), + pet("varbit.pet_menagerie_nex", "obj.nexpet"), + pet("varbit.pet_menagerie_scurrius", "obj.scurriuspet"), + pet("varbit.pet_menagerie_skotus", "obj.skotizopet"), + pet("varbit.pet_menagerie_tempoross", "obj.temporosspet"), + pet("varbit.pet_menagerie_vorki", "obj.vorkathpet"), + pet("varbit.pet_menagerie_duke_sucellus", "obj.dukesucelluspet"), + pet("varbit.pet_menagerie_leviathan", "obj.leviathanpet"), + pet("varbit.pet_menagerie_vardorvis", "obj.vardorvispet"), + pet("varbit.pet_menagerie_whisperer", "obj.whispererpet"), + pet("varbit.pet_menagerie_amoxliatl", "obj.amoxliatlpet"), + pet("varbit.pet_menagerie_huey", "obj.hueypet"), + pet("varbit.pet_menagerie_yama", "obj.yamapet"), + pet("varbit.pet_menagerie_solheredit", "obj.solhereditpet"), + pet("varbit.pet_menagerie_quetzal", "obj.quetzalpet"), + pet("varbit.pet_menagerie_dom", "obj.dompet"), + pet("varbit.pet_menagerie_abyssal", "obj.abyssalpet"), + // Skilling pets. + multiformPet( + "varbit.pet_menagerie_skillpethunter", + "varbit.poh_menagerie_multiform_skillpethunter", + "obj.skillpethunter_grey", + "obj.skillpethunter_red", + "obj.skillpethunter_black", + "obj.skillpethunter_gold", + ), + multiformPet( + "varbit.pet_menagerie_skillpetrunecraft", + "varbit.poh_menagerie_multiform_skillpetrunecrafting", + "obj.skillpetrunecrafting_air", + "obj.skillpetrunecrafting_mind", + "obj.skillpetrunecrafting_water", + "obj.skillpetrunecrafting_earth", + "obj.skillpetrunecrafting_fire", + "obj.skillpetrunecrafting_body", + "obj.skillpetrunecrafting_cosmic", + "obj.skillpetrunecrafting_chaos", + "obj.skillpetrunecrafting_nature", + "obj.skillpetrunecrafting_law", + "obj.skillpetrunecrafting_death", + "obj.skillpetrunecrafting_soul", + "obj.skillpetrunecrafting_astral", + "obj.skillpetrunecrafting_blood", + "obj.skillpetrunecrafting_wrath", + "obj.skillpetrunecrafting_gotr", + ), + multiformPet( + "varbit.pet_menagerie_skillpetmining", + "varbit.poh_menagerie_multiform_skillpetmining", + "obj.skillpetmining", + "obj.skillpetmining_tin", + "obj.skillpetmining_copper", + "obj.skillpetmining_iron", + "obj.skillpetmining_blurite", + "obj.skillpetmining_silver", + "obj.skillpetmining_coal", + "obj.skillpetmining_gold", + "obj.skillpetmining_mithril", + "obj.skillpetmining_granite", + "obj.skillpetmining_adamantite", + "obj.skillpetmining_runite", + "obj.skillpetmining_amethyst", + "obj.skillpetmining_lovakite", + "obj.skillpetmining_elemental", + "obj.skillpetmining_daeyalt", + "obj.skillpetmining_lead", + "obj.skillpetmining_rubium", + "obj.skillpetmining_nickel", + ), + multiformPet( + "varbit.pet_menagerie_skillpetwc", + "varbit.poh_menagerie_multiform_skillpetwoodcutting", + "obj.skillpetwc", + "obj.skillpet_wc_oak", + "obj.skillpet_wc_willow", + "obj.skillpet_wc_maple", + "obj.skillpet_wc_yew", + "obj.skillpet_wc_magic", + "obj.skillpet_wc_redwood", + "obj.skillpet_wc_mahogany", + "obj.skillpet_wc_teak", + "obj.skillpet_wc_arctic", + "obj.skillpet_wc_pheasant", + "obj.skillpet_wc_fox", + "obj.skillpet_wc_camphor", + "obj.skillpet_wc_ironwood", + "obj.skillpet_wc_jatoba", + "obj.skillpet_wc_rosewood", + ), + multiformPet( + "varbit.pet_menagerie_skillpetfish", + "varbit.poh_menagerie_multiform_skillpetfishing", + "obj.skillpetfish", + "obj.skillpetfish_tempoross", + ), + multiformPet( + "varbit.pet_menagerie_skillpetfarming", + "varbit.poh_menagerie_mutliform_farmingpet", + "obj.skillpetfarming", + "obj.skillpetfarming_crystal", + "obj.skillpetfarming_dragon", + "obj.skillpetfarming_herb", + "obj.skillpetfarming_lily", + "obj.skillpetfarming_redwood", + ), + multiformPet( + "varbit.pet_menagerie_skillpetthieving", + "varbit.poh_menagerie_multiform_thievingpet", + "obj.skillpetthieving", + "obj.skillpetthieving_panda", + "obj.skillpetthieving_tanuki", + ), + multiformPet( + "varbit.pet_menagerie_skillpetagility", + "varbit.poh_menagerie_multiform_skillpetagility_bigger", + "obj.skillpetagility", + "obj.skillpetagility_dark", + "obj.skillpetagility_bone", + ), + ) + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/PohDoorScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/PohDoorScript.kt new file mode 100644 index 000000000..a7d3c7150 --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/PohDoorScript.kt @@ -0,0 +1,129 @@ +package org.rsmod.content.skills.construction.features + +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.poh.PohManager +import org.rsmod.api.poh.PohStyleDoors +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.game.loc.LocAngle +import org.rsmod.game.loc.LocInfo +import org.rsmod.game.loc.LocShape +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Open/Close for the real house doors spawned on shared room passages outside building mode. + * + * The open variants keep their wall collision, so an opened leaf cannot stay on the doorway edge: + * following Zenyte's `HouseDoor.java`, opening moves each leaf onto the tile across the wall and + * turns it perpendicular (folded back against the doorway jamb), and closing moves it back. Both + * leaves of the two-tile doorway swing from a single click. Door columns always sit on the middle + * two positions (chunk-local 3/4) of a zone edge (chunk-local 0/7), which the swing geometry relies + * on. + */ +class PohDoorScript +@Inject +constructor(private val locRepo: LocRepository, private val manager: PohManager) : PluginScript() { + override fun ScriptContext.startup() { + for (doors in PohStyleDoors.allDoorLocs()) { + onOpLoc1(doors.closedLeft) { swing(it.loc, doors, open = true) } + onOpLoc1(doors.closedRight) { swing(it.loc, doors, open = true) } + onOpLoc1(doors.openLeft) { swing(it.loc, doors, open = false) } + onOpLoc1(doors.openRight) { swing(it.loc, doors, open = false) } + } + } + + private suspend fun ProtectedAccess.swing( + door: BoundLocInfo, + doors: PohStyleDoors, + open: Boolean, + ) { + arriveDelay() + soundSynth(if (open) DOOR_OPEN_SYNTH else DOOR_CLOSE_SYNTH) + val swaps = doors.stateSwaps(open) + // Both leaves sit in a row along the wall run; visit the clicked tile and its neighbours. + val (alongX, alongZ) = alongAxis(door.coords) + val row = + listOf( + door.coords, + door.coords.translate(alongX, alongZ), + door.coords.translate(-alongX, -alongZ), + ) + for (tile in row) { + for (info in locRepo.findAll(tile).toList()) { + val next = swaps[info.entity.id] ?: continue + locRepo.del(info, Int.MAX_VALUE) + // Despawning a spawned leaf resurrects the baked hotspot leaf underneath (the + // registry drops the shadowing record); re-hide it before placing the new state. + manager.hideDoorHotspot(player, tile) + if (open) { + openLeaf(tile, info, next) + } else { + closeLeaf(tile, next) + } + } + } + } + + /** Ids of the style's door locs in the current state, mapped to their target-state loc. */ + private fun PohStyleDoors.stateSwaps(open: Boolean): Map = + if (open) { + mapOf( + closedLeft.asRSCM(RSCMType.LOC) to openLeft, + closedRight.asRSCM(RSCMType.LOC) to openRight, + ) + } else { + mapOf( + openLeft.asRSCM(RSCMType.LOC) to closedLeft, + openRight.asRSCM(RSCMType.LOC) to closedRight, + ) + } + + /** The axis the door row runs along: doorways sit on chunk-local 0/7 edges. */ + private fun alongAxis(coords: CoordGrid): Pair { + val cx = coords.x and 0x7 + return if (cx == 0 || cx == 7) 0 to 1 else 1 to 0 + } + + /** Zenyte `openDoor`: move the leaf across the wall, turned against the doorway jamb. */ + private fun ProtectedAccess.openLeaf(tile: CoordGrid, closed: LocInfo, openLoc: String) { + val cx = tile.x and 0x7 + val cz = tile.z and 0x7 + val dest: CoordGrid + val angle: LocAngle + if (cz == 0 || cz == 7) { + dest = tile.translateZ(if (cz == 7) 1 else -1) + angle = if (cx == 3) LocAngle.West else LocAngle.East + } else { + dest = tile.translateX(if (cx == 7) 1 else -1) + angle = if (cz == 3) LocAngle.South else LocAngle.North + } + locRepo.add(dest, openLoc, Int.MAX_VALUE, angle, closed.shape) + } + + /** Zenyte `closeDoor`: move the leaf back onto the doorway edge, facing the wall run. */ + private fun ProtectedAccess.closeLeaf(tile: CoordGrid, closedLoc: String) { + val cx = tile.x and 0x7 + val cz = tile.z and 0x7 + val dest: CoordGrid + val angle: LocAngle + if (cz == 0 || cz == 7) { + dest = tile.translateZ(if (cz == 7) 1 else -1) + angle = if (cz == 7) LocAngle.South else LocAngle.North + } else { + dest = tile.translateX(if (cx == 7) 1 else -1) + angle = if (cx == 7) LocAngle.West else LocAngle.East + } + locRepo.add(dest, closedLoc, Int.MAX_VALUE, angle, LocShape.WallStraight) + } + + private companion object { + const val DOOR_OPEN_SYNTH = "synth.door_open" + const val DOOR_CLOSE_SYNTH = "synth.door_close" + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/PortalChamberScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/PortalChamberScript.kt new file mode 100644 index 000000000..df19f8e83 --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/PortalChamberScript.kt @@ -0,0 +1,169 @@ +package org.rsmod.content.skills.construction.features + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.vars.intVarBit +import org.rsmod.api.poh.PohManager +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.api.script.onOpLoc3 +import org.rsmod.game.entity.Player +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Destination coordinates shared by the portal chamber, portal nexus and future teleport rooms. + * + * The classic six city destinations reuse the exact coordinates silo's standard spellbook teleports + * resolve from `params.spell_telecoord` (plus their alternates), so a house portal and the + * equivalent spell always land on the same tile. Destinations without a silo spell (Kharyrll and + * the extended portal set) use the OSRS wiki teleport landing tiles. + */ +internal object PohTeleportDestinations { + val VARROCK = CoordGrid(3213, 3424, 0) + val GRAND_EXCHANGE = CoordGrid(3164, 3487, 0) + val LUMBRIDGE = CoordGrid(3221, 3218, 0) + val FALADOR = CoordGrid(2965, 3378, 0) + val CAMELOT = CoordGrid(2757, 3478, 0) + val SEERS_VILLAGE = CoordGrid(2725, 3485, 0) + val ARDOUGNE = CoordGrid(2661, 3302, 0) + val YANILLE = CoordGrid(2544, 3095, 0) + val WATCHTOWER = CoordGrid(2933, 4712, 0) + val KHARYRLL = CoordGrid(3492, 3471, 0) + val LUNAR_ISLE = CoordGrid(2113, 3915, 0) + val SENNTISTEN = CoordGrid(3322, 3336, 0) + val ANNAKARL = CoordGrid(3288, 3886, 0) + val WATERBIRTH = CoordGrid(2546, 3757, 0) + val FISHING_GUILD = CoordGrid(2611, 3391, 0) + val MARIM = CoordGrid(2797, 2798, 1) + val KOUREND = CoordGrid(1641, 3673, 0) +} + +private var Player.varrockPortalGeFirst by intVarBit("varbit.varrock_ge_teleport") +private var Player.camelotPortalSeersFirst by intVarBit("varbit.seers_camelot_teleport") +private var Player.yanillePortalYanilleFirst by intVarBit("varbit.yanille_teleport_location") + +/** + * Built portal-chamber portals: op1 `Enter` teleports to the destination encoded in the loc name. + * + * Varrock, Camelot and Yanille portals are multiloc parents whose varbit-selected children carry a + * two-destination op pair plus a `Toggle` op that swaps which destination is offered first + * (mirroring the spellbook's alternate teleports); the engine resolves op triggers against the + * child loc, so the bindings live on the `_1st` children. All other destinations are plain + * single-op portals. + * + * Not wired yet (no verified standard-teleport coordinate; the locs exist in the cache): the newer + * expansion portals - ape_atoll, arceuus_library, barrows, battlefront, carrallangar, catherby, + * cemetery, dareeyak, draynor_manor, fenkenstrain, fortis, ghorrock, harmony_island, iceplateau, + * khazard, lassar, mind_altar, ourania, paddewwa, respawn, salve_graveyard, stronghold, trollheim, + * weiss and west_ardougne. + */ +class PortalChamberScript @Inject constructor(private val manager: PohManager) : PluginScript() { + override fun ScriptContext.startup() { + for (material in MATERIALS) { + for ((suffix, destination) in SIMPLE_DESTINATIONS) { + onOpLoc1("loc.poh_portal_${material}_$suffix") { enterPortal(destination) } + } + } + for (material in CHILD_MATERIALS) { + registerTogglePortal( + material = material, + city = "varrock", + firstSuffix = "varrock1st", + secondSuffix = "ge1st", + firstDestination = PohTeleportDestinations.VARROCK, + secondDestination = PohTeleportDestinations.GRAND_EXCHANGE, + toggle = { varrockPortalGeFirst = 1 - varrockPortalGeFirst }, + ) + registerTogglePortal( + material = material, + city = "camelot", + firstSuffix = "camelot1st", + secondSuffix = "seers1st", + firstDestination = PohTeleportDestinations.CAMELOT, + secondDestination = PohTeleportDestinations.SEERS_VILLAGE, + toggle = { camelotPortalSeersFirst = 1 - camelotPortalSeersFirst }, + ) + registerTogglePortal( + material = material, + city = "yanille", + firstSuffix = "watchtower1st", + secondSuffix = "yanille1st", + firstDestination = PohTeleportDestinations.WATCHTOWER, + secondDestination = PohTeleportDestinations.YANILLE, + toggle = { yanillePortalYanilleFirst = 1 - yanillePortalYanilleFirst }, + ) + } + } + + /** + * Registers both varbit children of a two-destination portal. Each child's op1 targets its own + * primary destination, op2 the alternate, and op3 flips the varbit that decides which child the + * multiloc parent resolves to. + */ + private fun ScriptContext.registerTogglePortal( + material: String, + city: String, + firstSuffix: String, + secondSuffix: String, + firstDestination: CoordGrid, + secondDestination: CoordGrid, + toggle: Player.() -> Unit, + ) { + val first = "loc.poh_portal_${material}_${city}_$firstSuffix" + val second = "loc.poh_portal_${material}_${city}_$secondSuffix" + + onOpLoc1(first) { enterPortal(firstDestination) } + onOpLoc2(first) { enterPortal(secondDestination) } + onOpLoc3(first) { togglePortal(toggle) } + + onOpLoc1(second) { enterPortal(secondDestination) } + onOpLoc2(second) { enterPortal(firstDestination) } + onOpLoc3(second) { togglePortal(toggle) } + } + + private suspend fun ProtectedAccess.enterPortal(destination: CoordGrid) { + if (!manager.isInOwnHouse(player)) { + mes("You can only use house portals in your own house.") + return + } + arriveDelay() + telejump(destination) + mes("You step through the portal.") + } + + private suspend fun ProtectedAccess.togglePortal(toggle: Player.() -> Unit) { + if (!manager.isInOwnHouse(player)) { + mes("You can only do that in your own house.") + return + } + arriveDelay() + player.toggle() + mes("The portal's destination order has been switched.") + } + + private companion object { + /** Loc-name material prefixes of the single-destination portals. */ + val MATERIALS = listOf("teak", "mag", "marble") + + /** The varbit-child locs spell mahogany out in full, unlike their `mag` parents. */ + val CHILD_MATERIALS = listOf("teak", "mahogany", "marble") + + /** Loc-name suffix to destination for every single-op portal. */ + val SIMPLE_DESTINATIONS = + listOf( + "lumbridge" to PohTeleportDestinations.LUMBRIDGE, + "falador" to PohTeleportDestinations.FALADOR, + "ardougne" to PohTeleportDestinations.ARDOUGNE, + "kharyrll" to PohTeleportDestinations.KHARYRLL, + "lunarisle" to PohTeleportDestinations.LUNAR_ISLE, + "senntisten" to PohTeleportDestinations.SENNTISTEN, + "annakarl" to PohTeleportDestinations.ANNAKARL, + "waterbirth" to PohTeleportDestinations.WATERBIRTH, + "fishingguild" to PohTeleportDestinations.FISHING_GUILD, + "marim" to PohTeleportDestinations.MARIM, + "kourend" to PohTeleportDestinations.KOUREND, + ) + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/PortalNexusScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/PortalNexusScript.kt new file mode 100644 index 000000000..80682455b --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/PortalNexusScript.kt @@ -0,0 +1,62 @@ +package org.rsmod.content.skills.construction.features + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.poh.PohManager +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Built portal nexus (`loc.poh_nexus_portal_1..3` - marble/gilded/crystalline): op1 `Teleport` and + * op2 `Teleport Menu` both open a destination picker; the chosen destination is telejumped to with + * the same coordinate table the portal chamber uses. + * + * Simplification for this pass: on OSRS a nexus only offers destinations the owner has attuned to + * it (paying the rune cost once per destination, stored in `varbit.poh_nexus_tele_1..35`). + * Attunement is out of scope here, so the eight standard city destinations are offered directly and + * the attunement varbits are left untouched. When attunement lands, this menu should filter on + * those varbits instead, and op3 `Configuration` (currently unbound) should manage them. + */ +class PortalNexusScript @Inject constructor(private val manager: PohManager) : PluginScript() { + override fun ScriptContext.startup() { + for (nexus in NEXUS_PORTALS) { + onOpLoc1(nexus) { openTeleportMenu() } + onOpLoc2(nexus) { openTeleportMenu() } + } + } + + private suspend fun ProtectedAccess.openTeleportMenu() { + if (!manager.isInOwnHouse(player)) { + mes("You can only use the portal nexus in your own house.") + return + } + val index = menu(MENU_TITLE, hotkeys = true, choices = DESTINATIONS.map { it.first }) + val destination = DESTINATIONS.getOrNull(index)?.second ?: return + arriveDelay() + telejump(destination) + mes("You step through the portal.") + } + + private companion object { + const val MENU_TITLE = "Portal Nexus" + + val NEXUS_PORTALS = + listOf("loc.poh_nexus_portal_1", "loc.poh_nexus_portal_2", "loc.poh_nexus_portal_3") + + /** The eight standard city destinations, sharing the portal chamber's coordinates. */ + val DESTINATIONS: List> = + listOf( + "Varrock" to PohTeleportDestinations.VARROCK, + "Lumbridge" to PohTeleportDestinations.LUMBRIDGE, + "Falador" to PohTeleportDestinations.FALADOR, + "Camelot" to PohTeleportDestinations.CAMELOT, + "Ardougne" to PohTeleportDestinations.ARDOUGNE, + "Yanille" to PohTeleportDestinations.YANILLE, + "Kharyrll" to PohTeleportDestinations.KHARYRLL, + "Kourend Castle" to PohTeleportDestinations.KOUREND, + ) + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/ServantScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/ServantScript.kt new file mode 100644 index 000000000..848b40df7 --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/ServantScript.kt @@ -0,0 +1,447 @@ +package org.rsmod.content.skills.construction.features + +import dev.openrune.ServerCacheManager +import dev.openrune.rscm.RSCM.asRSCM +import dev.openrune.rscm.RSCMType +import dev.openrune.types.NpcMode +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.baseConstructionLvl +import org.rsmod.api.poh.PohDataStore +import org.rsmod.api.poh.PohHotspotSlot +import org.rsmod.api.poh.PohHouse +import org.rsmod.api.poh.PohHouseEnteredEvent +import org.rsmod.api.poh.PohHouseExitedEvent +import org.rsmod.api.poh.PohManager +import org.rsmod.api.poh.pohServantPay +import org.rsmod.api.poh.pohServantType +import org.rsmod.api.registry.npc.NpcRegistry +import org.rsmod.api.registry.npc.isSuccess +import org.rsmod.api.script.onEvent +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.game.entity.Npc +import org.rsmod.game.entity.Player +import org.rsmod.game.map.collision.isWalkBlocked +import org.rsmod.game.map.collision.isZoneValid +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext +import org.rsmod.routefinder.collision.CollisionFlagMap + +/** One of the five hireable servants; [type] is stored in `varbit.poh_servant_type`. */ +internal data class PohServant( + val type: Int, + val npc: String, + val displayName: String, + val level: Int, + val wage: Int, + val capacity: Int, +) + +/** Wiki-verified servant table: Construction level, wage per pay period and fetch capacity. */ +internal object PohServants { + val ALL: List = + listOf( + PohServant(1, "npc.poh_servant_dogsbody", "Rick", level = 20, wage = 500, capacity = 6), + PohServant( + 2, + "npc.poh_servant_waiter_woman", + "Maid", + level = 25, + wage = 1_000, + capacity = 10, + ), + PohServant( + 3, + "npc.poh_servant_cook_woman", + "Cook", + level = 30, + wage = 3_000, + capacity = 16, + ), + PohServant( + 4, + "npc.poh_servant_maitre_d_man", + "Butler", + level = 40, + wage = 5_000, + capacity = 20, + ), + PohServant( + 5, + "npc.poh_servant_demon", + "Demon butler", + level = 50, + wage = 10_000, + capacity = 26, + ), + ) + + fun forType(type: Int): PohServant? = ALL.firstOrNull { it.type == type } +} + +/** + * Spawns and reclaims the npcs that live inside a player's house: the hired servant (near the owner + * on entry) and the dungeon/oubliette/treasure-room guards (on their built guard hotspots, mapped + * through [PohGuards]). + * + * [respawn] is idempotent: spawned npcs are tracked per owner and deleted before every respawn, so + * repeated enter/rebuild events never duplicate them. Region reclaim also deletes npcs standing in + * unregistered zones (`RegionRegistry.clearAllZones`), so [despawn]'s deletes tolerate npcs that + * are already gone. Spawns go through [NpcRegistry.add] - the repository's `add(npc, + * Int.MAX_VALUE)` path double-registers permanent spawns. + */ +@Singleton +class PohNpcSpawner +@Inject +constructor( + private val manager: PohManager, + private val dataStore: PohDataStore, + private val npcRegistry: NpcRegistry, + private val collision: CollisionFlagMap, +) { + private val spawned = HashMap>() + + /** Clears any stale house npcs for [player] and spawns the current servant and guards. */ + fun respawn(player: Player) { + despawn(player) + val owner = player.uuid ?: return + val active = manager.activeHouse(player) ?: return + val tracked = mutableListOf() + spawnGuards(active, tracked) + spawnServant(player, active, tracked) + if (tracked.isNotEmpty()) { + spawned[owner] = tracked + } + } + + /** Deletes every npc previously spawned for [player]'s house. */ + fun despawn(player: Player) { + val owner = player.uuid ?: return + val tracked = spawned.remove(owner) ?: return + for (npc in tracked) { + // Region reclaim may have deleted the npc already; the failed delete is benign. + npcRegistry.del(npc) + } + } + + private fun spawnGuards(active: PohManager.ActiveHouse, tracked: MutableList) { + for ((slot, builtLoc) in active.house.furniture) { + val npcName = PohGuards.NPC_BY_BUILT_LOC[builtLoc] ?: continue + val room = active.house.rooms[slot.roomSlot] ?: continue + val hotspot = dataStore.hotspot(slot.hotspotIndex) + val coords = manager.hotspotCoords(active.region, slot.roomSlot, room, hotspot) + spawn(npcName, coords)?.let(tracked::add) + } + } + + private fun spawnServant( + player: Player, + active: PohManager.ActiveHouse, + tracked: MutableList, + ) { + val servant = PohServants.forType(player.pohServantType) ?: return + if (!manager.isInOwnHouse(player)) { + return + } + val coords = walkableNear(player.coords.translate(1, 0)) + spawn(servant.npc, coords)?.let(tracked::add) + } + + private fun spawn(npcName: String, coords: CoordGrid): Npc? { + val type = ServerCacheManager.getNpc(npcName.asRSCM(RSCMType.NPC)) ?: return null + val npc = Npc(type, coords) + npc.mode = NpcMode.None + val result = npcRegistry.add(npc) + return if (result.isSuccess()) npc else null + } + + private fun walkableNear(coords: CoordGrid): CoordGrid { + if (isWalkable(coords)) { + return coords + } + for (radius in 1..2) { + for (dx in -radius..radius) { + for (dz in -radius..radius) { + val candidate = coords.translate(dx, dz) + if (isWalkable(candidate)) { + return candidate + } + } + } + } + return coords + } + + private fun isWalkable(coords: CoordGrid): Boolean = + collision.isZoneValid(coords) && !collision.isWalkBlocked(coords) +} + +/** + * Servant hiring at the Servants' Guild and in-house servant services. + * + * Hiring (Talk-to on the guild npcs) requires no current servant, the wiki Construction level and + * two bedrooms with built beds. In the owner's house the hired servant offers fetch-from-bank + * (clamped to the servant's carry capacity), wage payment and dismissal. `varbit.poh_servant_pay` + * counts unpaid services: the servant demands wages after [DEMAND_THRESHOLD] and refuses further + * service at [REFUSE_THRESHOLD] until paid. + * + * TODO: sawmill runs, un-noting, take-to-bank, serving food, following the owner between rooms and + * the bell-pull "Call Servant" summon are not implemented yet. + */ +class ServantScript +@Inject +constructor( + private val manager: PohManager, + private val dataStore: PohDataStore, + private val spawner: PohNpcSpawner, +) : PluginScript() { + override fun ScriptContext.startup() { + onEvent { spawner.respawn(player) } + onEvent { spawner.despawn(player) } + for (servant in PohServants.ALL) { + onOpNpc1(servant.npc) { servantTalk(it.npc, servant) } + } + } + + private suspend fun ProtectedAccess.servantTalk(npc: Npc, servant: PohServant) { + if (manager.isInOwnHouse(player) && player.pohServantType == servant.type) { + startDialogue(npc) { houseService(servant) } + } else { + startDialogue(npc) { guildDialogue(servant) } + } + } + + /* Servants' Guild hiring */ + + private suspend fun Dialogue.guildDialogue(servant: PohServant) { + val player = access.player + when { + player.pohServantType == servant.type -> + chatNpc(happy, "I already work for you! You'll find me waiting in your house.") + player.pohServantType != 0 -> + chatNpc( + neutral, + "You already employ a servant. You'll have to dismiss them before you can " + + "hire me.", + ) + player.baseConstructionLvl < servant.level -> + chatNpc( + neutral, + "I'm sorry, but I only work for masters with a Construction level of at " + + "least ${servant.level}.", + ) + else -> offerHire(servant) + } + } + + private suspend fun Dialogue.offerHire(servant: PohServant) { + val house = manager.houseOf(access.player) + if (house == null) { + chatNpc(neutral, "You'll need to own a house before you can hire a servant.") + return + } + if (bedroomsWithBeds(house) < REQUIRED_BEDROOMS) { + chatNpc( + neutral, + "Unfortunately I am only able to work in houses with at least two bedrooms " + + "that have beds in them.", + ) + return + } + chatNpc( + happy, + "Good day! I charge wages of ${servant.wage} coins, collected after every " + + "$REFUSE_THRESHOLD tasks I perform for you.", + ) + val hire = + choice2( + "You're hired!", + true, + "Maybe another time.", + false, + title = "Hire the ${servant.displayName} for ${servant.wage} coins in wages?", + ) + if (!hire) { + chatPlayer(neutral, "Maybe another time.") + return + } + access.player.pohServantType = servant.type + access.player.pohServantPay = 0 + chatNpc(happy, "Excellent! I shall meet you at your house.") + } + + /* In-house services */ + + private suspend fun Dialogue.houseService(servant: PohServant) { + if (access.player.pohServantPay >= REFUSE_THRESHOLD && !demandPayment(servant)) { + return + } + chatNpc(happy, "Yes? How may I serve you?") + val choice = + choice4( + "Fetch something from the bank.", + OPT_FETCH, + "Here, take your payment.", + OPT_PAY, + "You're fired.", + OPT_FIRE, + "Never mind.", + OPT_CANCEL, + ) + when (choice) { + OPT_FETCH -> fetchFromBank(servant) + OPT_PAY -> payWages(servant) + OPT_FIRE -> fireServant(servant) + OPT_CANCEL -> chatPlayer(neutral, "Never mind.") + } + } + + /** + * Simple fetch-from-bank service: moves unnoted materials straight from the player's bank inv + * into their inventory, clamped to the servant's capacity and free inventory space. + * + * TODO: live servants physically walk to the bank, support arbitrary items/noted fetches and + * hold undelivered items until wages are paid. + */ + private suspend fun Dialogue.fetchFromBank(servant: PohServant) { + chatPlayer(neutral, "Could you fetch something from my bank?") + val available = FETCH_ITEMS.filter { access.invTotal(access.bank, it.obj) > 0 } + if (available.isEmpty()) { + chatNpc(sad, "I'm afraid there are no building materials in your bank for me to fetch.") + return + } + val index = + access.menu("Fetch which material?", hotkeys = false, available.map { it.label }) + val item = available.getOrNull(index) ?: return + val requested = access.countDialog("How many? (I can carry up to ${servant.capacity}.)") + if (requested <= 0) { + return + } + val amount = + minOf( + requested, + servant.capacity, + access.invTotal(access.bank, item.obj), + access.inv.freeSpace(), + ) + if (amount <= 0) { + chatNpc(sad, "You don't have any room to hold what I would bring back.") + return + } + access.delay(TRIP_DELAY) + access.invDel(access.bank, item.obj, amount) + access.invAdd(access.inv, item.obj, amount) + access.player.pohServantPay += 1 + chatNpc(happy, "Here you are: $amount x ${item.label}.") + if (access.player.pohServantPay == DEMAND_THRESHOLD) { + demandPayment(servant) + } + } + + /** Demands the servant's wages; returns `true` when the player pays. */ + private suspend fun Dialogue.demandPayment(servant: PohServant): Boolean { + chatNpc( + neutral, + "Before I do anything else, I must ask for my wages of ${servant.wage} coins.", + ) + val pay = choice2("Okay, here you go.", true, "Not right now.", false) + if (!pay) { + chatNpc(sad, "Then I'm afraid I can do nothing more for you until I am paid.") + return false + } + if (access.invTotal(access.inv, COINS) < servant.wage) { + chatPlayer(sad, "I don't seem to have enough coins on me.") + return false + } + access.invDel(access.inv, COINS, servant.wage) + access.player.pohServantPay = 0 + chatNpc(happy, "Thank you very much.") + return true + } + + private suspend fun Dialogue.payWages(servant: PohServant) { + if (access.player.pohServantPay == 0) { + chatNpc(happy, "You don't owe me anything yet.") + return + } + if (access.invTotal(access.inv, COINS) < servant.wage) { + chatNpc(sad, "I'm afraid you don't have my wages of ${servant.wage} coins on you.") + return + } + access.invDel(access.inv, COINS, servant.wage) + access.player.pohServantPay = 0 + chatNpc(happy, "Thank you very much.") + } + + private suspend fun Dialogue.fireServant(servant: PohServant) { + val confirm = + choice2( + "Yes, you're fired.", + true, + "Actually, never mind.", + false, + title = "Dismiss the ${servant.displayName}?", + ) + if (!confirm) { + return + } + access.player.pohServantType = 0 + access.player.pohServantPay = 0 + chatNpc(sad, "Very well. It has been a pleasure serving you.") + spawner.respawn(access.player) + } + + /** Counts bedrooms whose "Bed space" hotspot holds a built bed. */ + private fun bedroomsWithBeds(house: PohHouse): Int { + val bedIndices = + dataStore.hotspots(BEDROOM_ROOM).filter { it.name == BED_SPACE_NAME }.map { it.index } + return house.rooms.count { (slot, room) -> + room.type == BEDROOM_ROOM && + bedIndices.any { index -> + PohHotspotSlot(slot.level, slot.gridX, slot.gridZ, index) in house.furniture + } + } + } + + private data class FetchItem(val obj: String, val label: String) + + private companion object { + const val OPT_FETCH = 1 + const val OPT_PAY = 2 + const val OPT_FIRE = 3 + const val OPT_CANCEL = 4 + + const val COINS = "obj.coins" + const val BEDROOM_ROOM = "bedroom" + const val BED_SPACE_NAME = "Bed space" + const val REQUIRED_BEDROOMS = 2 + + /** After this many unpaid services the servant asks for wages. */ + const val DEMAND_THRESHOLD = 7 + + /** At this many unpaid services the servant refuses to work until paid. */ + const val REFUSE_THRESHOLD = 8 + + /** Cycles the "trip" to the bank takes. */ + const val TRIP_DELAY = 2 + + /** Construction materials the servant can fetch from the bank (RSCM names verified). */ + val FETCH_ITEMS = + listOf( + FetchItem("obj.woodplank", "Plank"), + FetchItem("obj.plank_oak", "Oak plank"), + FetchItem("obj.plank_teak", "Teak plank"), + FetchItem("obj.plank_mahogany", "Mahogany plank"), + FetchItem("obj.softclay", "Soft clay"), + FetchItem("obj.limestonebrick", "Limestone brick"), + FetchItem("obj.steel_bar", "Steel bar"), + FetchItem("obj.cloth", "Bolt of cloth"), + FetchItem("obj.gold_leaf", "Gold leaf"), + FetchItem("obj.marble_block", "Marble block"), + FetchItem("obj.poh_magic_crystal", "Magic stone"), + ) + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/StairsScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/StairsScript.kt new file mode 100644 index 000000000..4f650f78c --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/features/StairsScript.kt @@ -0,0 +1,302 @@ +package org.rsmod.content.skills.construction.features + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.baseConstructionLvl +import org.rsmod.api.poh.PohConstants +import org.rsmod.api.poh.PohDataStore +import org.rsmod.api.poh.PohHotspotSlot +import org.rsmod.api.poh.PohManager +import org.rsmod.api.poh.PohRoom +import org.rsmod.api.poh.PohRoomSlot +import org.rsmod.api.poh.pohBuildingMode +import org.rsmod.api.repo.loc.LocRepository +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.api.script.onOpLoc3 +import org.rsmod.content.skills.construction.PohStairs +import org.rsmod.game.loc.BoundLocInfo +import org.rsmod.game.map.collision.isZoneValid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext +import org.rsmod.routefinder.collision.CollisionFlagMap + +/** + * Built staircases, spiral staircases, dungeon ladders and throne-room trapdoors. + * + * Movement is a straight level translation at the player's tile: hall staircases + * (`loc.poh_stairs_3..5` op1 `Climb-up`, `loc.poh_stairstop_3..5` op1 `Climb-down`) link the ground + * and upper floors; spiral staircases (`loc.poh_spiralstairs`, `loc.poh_spiralstairs_2`, ops + * `Climb`/`Climb-up`/`Climb-down`) and oubliette ladders (`loc.poh_dungeon_ladder_*`, op1 `Climb`) + * resolve the combined `Climb` op to whichever direction has a valid destination zone, asking when + * both are valid. Throne-room trapdoors (`loc.poh_trapdoor_{oak,teak,mag}_7`) open into their + * `_open_7` variants, whose `Go-down` op descends into the dungeon (house level 0). + * + * Every move is guarded by `collision.isZoneValid` on the destination - the region builder only + * allocates zones for floors that hold rooms, so climbing towards a missing floor reports "There is + * nothing above/below." instead of teleporting into the void. Climb-up is additionally capped at + * the upper floor so stairs can never lead onto the roof level. + */ +class StairsScript +@Inject +constructor( + private val collision: CollisionFlagMap, + private val locRepo: LocRepository, + private val manager: PohManager, + private val dataStore: PohDataStore, +) : PluginScript() { + override fun ScriptContext.startup() { + for (stairs in STAIRCASES) { + onOpLoc1(stairs) { climb(UP, connector = stairs) } + } + for (stairtop in STAIRCASE_TOPS) { + onOpLoc1(stairtop) { climb(DOWN, connector = stairtop) } + } + for (spiral in SPIRAL_STAIRCASES) { + onOpLoc1(spiral) { climbEither(connector = spiral) } + onOpLoc2(spiral) { climb(UP, connector = spiral) } + onOpLoc3(spiral) { climb(DOWN, connector = spiral) } + } + onOpLoc1(DUNGEON_ENTRANCE) { climb(DOWN, connector = DUNGEON_ENTRANCE) } + for (ladder in DUNGEON_LADDERS) { + onOpLoc1(ladder) { climbEither(ladder = true) } + } + for ((closed, open) in TRAPDOORS) { + onOpLoc1(closed) { openTrapdoor(it.loc, open) } + onOpLoc1(open) { descendTrapdoor() } + onOpLoc2(open) { closeTrapdoor(it.loc, closed) } + } + } + + private suspend fun ProtectedAccess.climb( + direction: Int, + ladder: Boolean = false, + connector: String? = null, + ) { + if (!canClimbTo(direction)) { + if (!offerStairRoom(direction, ladder, connector)) { + mes(if (direction > 0) "There is nothing above." else "There is nothing below.") + } + return + } + arriveDelay() + if (ladder) { + anim(if (direction > 0) LADDER_UP_ANIM else LADDER_DOWN_ANIM) + delay(1) + } + telejump(player.coords.translateLevel(direction)) + } + + /** + * Resolves a combined `Climb` op: single valid direction climbs immediately; both valid asks + * which way; neither reports there is nowhere to go. + */ + private suspend fun ProtectedAccess.climbEither( + ladder: Boolean = false, + connector: String? = null, + ) { + val canUp = canClimbTo(UP) + val canDown = canClimbTo(DOWN) + when { + canUp && canDown -> { + val up = + choice2( + "Climb up.", + true, + "Climb down.", + false, + title = "Climb up or down the ${if (ladder) "ladder" else "stairs"}?", + ) + climb(if (up) UP else DOWN, ladder, connector) + } + canUp -> climb(UP, ladder, connector) + canDown -> climb(DOWN, ladder, connector) + else -> mes("These ${if (ladder) "ladders" else "stairs"} don't lead anywhere.") + } + } + + /** + * Zenyte `ClimbEmptyStaircaseD` (live-captured dialogue): climbing toward a missing room in + * building mode offers to build the stair-linked room instead - halls above, halls or the + * dungeon stairs room below. The new room copies the current room's rotation so the stairwells + * line up, and per the wiki the matching connector is mirrored into it: spiral staircases + * consume a second set of their materials, hall staircases continue for free as their stair-top + * piece. Returns false when the offer doesn't apply and the plain "nothing above/below" message + * should be shown. + */ + private suspend fun ProtectedAccess.offerStairRoom( + direction: Int, + ladder: Boolean, + connector: String?, + ): Boolean { + if (player.pohBuildingMode != 1) { + return false + } + val destLevel = player.coords.level + direction + if (destLevel < DUNGEON_LEVEL || destLevel > UPPER_LEVEL) { + return false + } + val active = manager.activeHouse(player) ?: return false + val slot = manager.roomSlotAt(active.region, player.coords) ?: return false + val current = active.house.rooms[slot] ?: return false + val target = PohRoomSlot(destLevel, slot.gridX, slot.gridZ) + if (active.house.rooms.containsKey(target)) { + return false + } + if (active.house.rooms.size >= PohConstants.maxRooms(player.baseConstructionLvl)) { + mes("You can't build any more rooms at your Construction level.") + return true + } + val options = + if (direction == DOWN && destLevel == DUNGEON_LEVEL) STAIR_DOWN_ROOMS + else STAIR_UP_ROOMS + val end = if (direction == UP) "top" else "bottom" + mesbox("These stairs do not lead anywhere. Do you want to build a room at the $end?") + val labels = options.map { "${it.label} (${it.cost} coins) - level ${it.level}" } + "Cancel" + val index = menu("Select an option", hotkeys = false, labels) + val option = options.getOrNull(index) ?: return true + if (player.baseConstructionLvl < option.level) { + mes("You need a Construction level of ${option.level} to build that room.") + return true + } + if (invTotal(inv, COINS) < option.cost) { + mes("You can't afford to build that room.") + return true + } + val mirror = PohStairs.MIRRORS[connector] + if (mirror != null) { + for ((material, count) in mirror.materials) { + if (invTotal(inv, material) < count) { + mes("You don't have the materials to build the matching staircase.") + return true + } + } + } + invDel(inv, COINS, option.cost) + mirror?.materials?.forEach { (material, count) -> invDel(inv, material, count) } + manager.addRoom(player, target, PohRoom(option.roomKey, current.rotation)) + mes("You build a ${option.label.lowercase()}.") + if (mirror != null) { + val stairSpace = + dataStore.hotspots(option.roomKey).firstOrNull { + it.name == PohStairs.STAIR_SPACE_NAME + } + if (stairSpace != null) { + manager.applyFurniture( + player, + PohHotspotSlot(target.level, target.gridX, target.gridZ, stairSpace.index), + mirror.builtLoc, + ) + } + } + climb(direction, ladder, connector) + return true + } + + private fun ProtectedAccess.canClimbTo(direction: Int): Boolean { + // Check the level arithmetic before building the coordinate: CoordGrid rejects levels + // outside [0..3], and climbing down at the dungeon level would otherwise throw (and + // disconnect the player) instead of reporting there is nothing below. + val destLevel = player.coords.level + direction + if (destLevel < DUNGEON_LEVEL || destLevel > UPPER_LEVEL) { + return false + } + val dest = player.coords.translateLevel(direction) + return collision.isZoneValid(dest) + } + + private suspend fun ProtectedAccess.openTrapdoor(trapdoor: BoundLocInfo, openLoc: String) { + arriveDelay() + soundSynth(DOOR_OPEN_SYNTH) + locRepo.change(trapdoor, openLoc, TRAPDOOR_REVERT_TICKS) + } + + private suspend fun ProtectedAccess.closeTrapdoor(trapdoor: BoundLocInfo, closedLoc: String) { + arriveDelay() + soundSynth(DOOR_CLOSE_SYNTH) + locRepo.change(trapdoor, closedLoc, TRAPDOOR_REVERT_TICKS) + } + + private suspend fun ProtectedAccess.descendTrapdoor() { + if (!canClimbTo(DOWN)) { + mes("There is nothing below.") + return + } + arriveDelay() + anim(TRAPDOOR_DOWN_ANIM) + delay(1) + telejump(player.coords.translateLevel(DOWN)) + } + + /** A stair-linked room offer: wiki level and cost gates, template key for the target floor. */ + private data class StairRoomOption( + val label: String, + val roomKey: String, + val level: Int, + val cost: Int, + ) + + private companion object { + const val UP = 1 + + const val COINS = "obj.coins" + const val DOWN = -1 + + /** House destination levels: 0 = dungeon, 1 = ground, 2 = upper floor; 3 is roof-only. */ + const val DUNGEON_LEVEL = 0 + const val UPPER_LEVEL = 2 + + const val LADDER_UP_ANIM = "seq.human_reachforladder" + const val LADDER_DOWN_ANIM = "seq.human_pickupfloor" + const val TRAPDOOR_DOWN_ANIM = "seq.human_pickupfloor" + const val DOOR_OPEN_SYNTH = "synth.door_open" + const val DOOR_CLOSE_SYNTH = "synth.door_close" + + /** Ticks an opened/closed trapdoor overlay persists before reverting to the built loc. */ + const val TRAPDOOR_REVERT_TICKS = 100 + + /** Hall staircases: oak / teak / marble bottom halves, op1 `Climb-up`. */ + val STAIRCASES = listOf("loc.poh_stairs_3", "loc.poh_stairs_4", "loc.poh_stairs_5") + + /** Matching upper-floor staircase tops, op1 `Climb-down`. */ + val STAIRCASE_TOPS = + listOf("loc.poh_stairstop_3", "loc.poh_stairstop_4", "loc.poh_stairstop_5") + + /** Limestone and marble spiral staircases, ops `Climb`/`Climb-up`/`Climb-down`. */ + val SPIRAL_STAIRCASES = listOf("loc.poh_spiralstairs", "loc.poh_spiralstairs_2") + + /** Garden/formal-garden centrepiece "Dungeon entrance", op1 `Enter` (descends). */ + const val DUNGEON_ENTRANCE = "loc.poh_crude_garden_centrepiece5" + + /** Oubliette ladder-space builds, op1 `Climb`. */ + val DUNGEON_LADDERS = + listOf( + "loc.poh_dungeon_ladder_oak", + "loc.poh_dungeon_ladder_teak", + "loc.poh_dungeon_ladder_mag", + ) + + /** Throne-room trapdoors: closed built loc to its open runtime variant. */ + val TRAPDOORS = + listOf( + "loc.poh_trapdoor_oak_7" to "loc.poh_trapdoor_oak_open_7", + "loc.poh_trapdoor_teak_7" to "loc.poh_trapdoor_teak_open_7", + "loc.poh_trapdoor_mag_7" to "loc.poh_trapdoor_mag_open_7", + ) + + /** Rooms offered when ascending toward an empty slot (capture: "Skill hall|Quest hall"). */ + val STAIR_UP_ROOMS = + listOf( + StairRoomOption("Skill hall", "skill_hall_stairs_top", 25, 15_000), + StairRoomOption("Quest hall", "quest_hall_stairs_top", 35, 25_000), + ) + + /** Rooms offered descending into the dungeon level (capture adds the stairs room). */ + val STAIR_DOWN_ROOMS = + listOf( + StairRoomOption("Skill hall", "skill_hall_stairs_up", 25, 15_000), + StairRoomOption("Quest hall", "quest_hall_stairs_up", 35, 25_000), + StairRoomOption("Dungeon stairs room", "dungeon_stairs", 70, 7_500), + ) + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/EstateAgentScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/EstateAgentScript.kt new file mode 100644 index 000000000..5e9783828 --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/EstateAgentScript.kt @@ -0,0 +1,185 @@ +package org.rsmod.content.skills.construction.scripts + +import jakarta.inject.Inject +import org.rsmod.api.player.dialogue.Dialogue +import org.rsmod.api.player.stat.baseConstructionLvl +import org.rsmod.api.poh.PohConstants +import org.rsmod.api.poh.PohLocation +import org.rsmod.api.poh.PohManager +import org.rsmod.api.poh.pohHouseLocation +import org.rsmod.api.poh.pohHouseStyle +import org.rsmod.api.script.onOpNpc1 +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The Estate agent: sells the starter house, relocates it between the house-portal cities, + * redecorates it into the 13 cache styles, and stocks the Construction cape at 99. + */ +class EstateAgentScript @Inject constructor(private val manager: PohManager) : PluginScript() { + override fun ScriptContext.startup() { + onOpNpc1("npc.poh_estate_agent") { startDialogue(it.npc) { agentDialogue() } } + } + + private suspend fun Dialogue.agentDialogue() { + chatNpc(happy, "Good day! How may I help you?") + if (!manager.hasHouse(access.player)) { + offerHouse() + return + } + val choice = + choice4( + "Could you move my house please?", + OPT_MOVE, + "Could you redecorate my house please?", + OPT_REDECORATE, + "Can I buy a Construction cape?", + OPT_CAPE, + "Never mind.", + OPT_NEVER_MIND, + ) + when (choice) { + OPT_MOVE -> relocateBranch() + OPT_REDECORATE -> redecorateBranch() + OPT_CAPE -> capeBranch() + OPT_NEVER_MIND -> chatPlayer(neutral, "Never mind.") + } + } + + private suspend fun Dialogue.offerHouse() { + chatNpc( + happy, + "I see you don't own a house yet. I can sell you a starter home in Rimmington " + + "for just ${PohConstants.HOUSE_COST} coins. It comes with a parlour and a garden.", + ) + val buy = + choice2( + "Yes please!", + true, + "No thanks.", + false, + title = "Buy a house for ${PohConstants.HOUSE_COST} coins?", + ) + if (!buy) { + chatPlayer(neutral, "No thanks.") + return + } + if (access.invTotal(access.inv, COINS) < PohConstants.HOUSE_COST) { + chatNpc(sad, "I'm afraid you don't have enough coins for that.") + return + } + access.invDel(access.inv, COINS, PohConstants.HOUSE_COST) + manager.createHouse(access.player) + chatNpc( + happy, + "Congratulations on your new home! You'll find the house portal just south of " + + "Rimmington. Enter in building mode to start furnishing it.", + ) + } + + private suspend fun Dialogue.relocateBranch() { + chatPlayer(quiz, "Could you move my house please?") + val current = PohLocation.forVarValue(access.player.pohHouseLocation) + val destinations = PohLocation.entries.filter { it != current } + val labels = destinations.map { "${it.displayName} (${it.relocateCost} coins)" } + val index = access.menu("Where would you like your house?", hotkeys = false, labels) + val destination = destinations.getOrNull(index) ?: return + if (access.invTotal(access.inv, COINS) < destination.relocateCost) { + chatNpc(sad, "I'm afraid you don't have enough coins for that.") + return + } + access.invDel(access.inv, COINS, destination.relocateCost) + access.player.pohHouseLocation = destination.varValue + chatNpc(happy, "Done! Your house is now in ${destination.displayName}.") + } + + private suspend fun Dialogue.redecorateBranch() { + chatPlayer(quiz, "Could you redecorate my house please?") + val labels = STYLES.map { "${it.label} (${it.cost} coins) - level ${it.level}" } + val index = access.menu("Which style would you like?", hotkeys = false, labels) + val style = STYLES.getOrNull(index) ?: return + if (access.player.baseConstructionLvl < style.level) { + chatNpc( + sad, + "You need a Construction level of ${style.level} for the ${style.label} style.", + ) + return + } + if (access.invTotal(access.inv, COINS) < style.cost) { + chatNpc(sad, "I'm afraid you don't have enough coins for that.") + return + } + access.invDel(access.inv, COINS, style.cost) + access.player.pohHouseStyle = style.index + chatNpc(happy, "Done! Your house has been redecorated in the ${style.label} style.") + } + + private suspend fun Dialogue.capeBranch() { + chatPlayer(quiz, "Can I buy a Construction cape?") + if (access.player.baseConstructionLvl < 99) { + chatNpc( + neutral, + "The Construction cape is only for those who have mastered the skill. Come " + + "back when you've reached level 99.", + ) + return + } + val buy = + choice2( + "Yes please.", + true, + "No thanks.", + false, + title = "Buy a Construction cape for $CAPE_COST coins?", + ) + if (!buy) { + chatPlayer(neutral, "No thanks.") + return + } + if (access.invTotal(access.inv, COINS) < CAPE_COST) { + chatNpc(sad, "I'm afraid you don't have enough coins for that.") + return + } + access.invDel(access.inv, COINS, CAPE_COST) + access.invAdd(access.inv, CONSTRUCTION_CAPE, 1) + access.invAdd(access.inv, CONSTRUCTION_HOOD, 1) + chatNpc(happy, "Wear it with pride - you've earned it.") + } + + private data class StyleOption( + val index: Int, + val label: String, + val level: Int, + val cost: Int, + ) + + private companion object { + const val OPT_MOVE = 1 + const val OPT_REDECORATE = 2 + const val OPT_CAPE = 3 + const val OPT_NEVER_MIND = 4 + + const val COINS = "obj.coins" + const val CONSTRUCTION_CAPE = "obj.skillcape_construction" + const val CONSTRUCTION_HOOD = "obj.skillcape_construction_hood" + const val CAPE_COST = 99_000 + + /** Style slots from the datagen table; classic styles use the wiki level/cost gates. */ + val STYLES = + listOf( + StyleOption(0, "Basic wood", level = 1, cost = 5_000), + StyleOption(1, "Basic stone", level = 10, cost = 5_000), + StyleOption(2, "Whitewashed stone", level = 20, cost = 7_500), + StyleOption(3, "Fremennik-style wood", level = 30, cost = 10_000), + StyleOption(4, "Tropical wood", level = 40, cost = 15_000), + StyleOption(5, "Fancy stone", level = 50, cost = 25_000), + StyleOption(6, "Deathly mansion", level = 1, cost = 25_000), + StyleOption(7, "Twisted theme", level = 1, cost = 25_000), + StyleOption(8, "Hosidius theme", level = 1, cost = 25_000), + StyleOption(9, "Cosy cabin", level = 1, cost = 25_000), + StyleOption(10, "Civitas illa fortis", level = 1, cost = 25_000), + StyleOption(11, "Canifis theme", level = 1, cost = 25_000), + StyleOption(12, "Wilderness theme", level = 1, cost = 25_000), + ) + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/FurnitureScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/FurnitureScript.kt new file mode 100644 index 000000000..76b8ee44c --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/FurnitureScript.kt @@ -0,0 +1,391 @@ +package org.rsmod.content.skills.construction.scripts + +import dev.openrune.definition.type.widget.IfEvent +import dev.openrune.types.aconverted.interf.IfSubType +import jakarta.inject.Inject +import org.rsmod.api.player.input.ResumePauseButtonInput +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.righthand +import org.rsmod.api.player.stat.baseConstructionLvl +import org.rsmod.api.poh.PohDataStore +import org.rsmod.api.poh.PohFloor +import org.rsmod.api.poh.PohHotspotSlot +import org.rsmod.api.poh.PohHouse +import org.rsmod.api.poh.PohManager +import org.rsmod.api.poh.PohRoomSlot +import org.rsmod.api.poh.pohBuildingMode +import org.rsmod.api.script.onOpLoc5 +import org.rsmod.api.table.PohFurnitureRow +import org.rsmod.content.skills.construction.PohFurniture +import org.rsmod.content.skills.construction.PohFurnitureIcons +import org.rsmod.content.skills.construction.PohStairs +import org.rsmod.game.entity.Player +import org.rsmod.game.stat.PlayerStatMap +import org.rsmod.map.CoordGrid +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Building and removing furniture on hotspots. + * + * The build menu follows the captured `interface.poh_furniture_creation` wire format exactly: + * `runclientscript 1404` per option, `runclientscript 1406 [count, false]`, pause-button events on + * `component.poh_furniture_creation:contents` (458:2), then a `resume_pausebutton` reply carrying + * the chosen slot. + */ +class FurnitureScript +@Inject +constructor(private val manager: PohManager, private val dataStore: PohDataStore) : PluginScript() { + override fun ScriptContext.startup() { + val hotspotLocs = dataStore.hotspots.map { it.loc }.distinct() + for (hotspotLoc in hotspotLocs) { + onOpLoc5(hotspotLoc) { onHotspotBuild(it.loc.coords, it.type.id) } + } + for (builtLoc in PohFurniture.allBuiltLocs()) { + onOpLoc5(builtLoc) { onFurnitureRemove(it.loc.coords, it.type.id) } + } + } + + private suspend fun ProtectedAccess.onHotspotBuild(coords: CoordGrid, locId: Int) { + if (player.pohBuildingMode != 1) { + mes("You can only do that in building mode.") + return + } + val (hotspotSlot, hotspot) = manager.findHotspot(player, coords, locId) ?: return + val options = PohFurniture.optionsFor(locId) + if (options.isEmpty()) { + mes("You can't build anything there yet.") + return + } + + val chosen = openBuildMenu(options) ?: return + + // Stair connectors exist in pairs: mirroring into an empty linked stair space needs the + // matching materials up front (wiki: "resources to build a matching staircase"). + val builtLoc = PohFurniture.builtLocFor(chosen, locId)?.internalName + val mirrorTargets = + if (builtLoc != null) linkedEmptyStairSpaces(hotspotSlot, builtLoc) else emptyList() + val mirrorMaterials = + if (mirrorTargets.isEmpty()) emptyList() else mirrorMaterials(builtLoc!!) + + if (player.baseConstructionLvl < chosen.level) { + mes("You need a Construction level of ${chosen.level} to build this.") + return + } + if (!player.hasHammerAndSaw()) { + mes("You need a hammer and a saw to build furniture.") + return + } + val nailType = selectNails(chosen.nails) + if (!hasMaterials(chosen) || (chosen.nails > 0 && nailType == null)) { + mes("You don't have enough materials to build that.") + return + } + for ((material, count) in mirrorMaterials) { + if (invTotal(inv, material) < count) { + mes("You don't have the materials to build the matching staircase.") + return + } + } + + playBuildAnim(hotspot.shape) + delay(2) + if (!driveNails(chosen.nails)) { + mes("You've run out of nails.") + return + } + consumeMaterials(chosen) + mirrorMaterials.forEach { (material, count) -> invDel(inv, material, count) } + + statAdvance("stat.construction", chosen.xp / PlayerStatMap.XP_FINE_PRECISION.toDouble()) + applyBuild(chosen, hotspotSlot, locId) + if (mirrorTargets.isNotEmpty()) { + val mirror = PohStairs.MIRRORS.getValue(builtLoc!!).builtLoc + for (target in mirrorTargets) { + manager.applyFurniture(player, target, mirror) + } + } + } + + private suspend fun ProtectedAccess.onFurnitureRemove(coords: CoordGrid, locId: Int) { + if (player.pohBuildingMode != 1) { + mes("You can only do that in building mode.") + return + } + val (hotspotSlot, _) = manager.findBuiltFurniture(player, coords, locId) ?: return + val house = manager.houseOf(player) ?: return + val removedLoc = house.furniture[hotspotSlot] + val row = PohFurniture.rowsForBuilt(locId).firstOrNull() ?: return + + val confirm = choice2("Yes.", true, "No.", false, title = "Really remove it?") + if (!confirm) { + return + } + + anim(REMOVE_ANIM) + delay(2) + + if (row.groupBuild) { + val roomSlot = hotspotSlot.roomSlot + val builtIds = row.builtLoc.map { it.id }.toSet() + val grouped = + house.furnitureAt(roomSlot).filterValues { locName -> + dev.openrune.rscm.RSCM.getRSCM(locName) in builtIds + } + for (slot in grouped.keys) { + manager.removeFurniture(player, slot) + } + } else { + manager.removeFurniture(player, hotspotSlot) + } + if (removedLoc != null) { + removeLinkedStairs(house, hotspotSlot, removedLoc) + } + } + + /** Empty linked stair spaces a pair-building connector at [slot] would mirror into. */ + private fun ProtectedAccess.linkedEmptyStairSpaces( + slot: PohHotspotSlot, + builtLoc: String, + ): List { + val house = manager.houseOf(player) ?: return emptyList() + return buildList { + for (direction in PohStairs.linkedDirections(builtLoc)) { + val linked = + stairSpaceSlot(house, slot, slot.roomSlot.level + direction) ?: continue + if (house.furniture[linked] == null) { + add(linked) + } + } + } + } + + private fun mirrorMaterials(builtLoc: String): List> = + PohStairs.MIRRORS[builtLoc]?.materials.orEmpty() + + /** + * Removes the other half of a stair connector, walking spiral chains across levels so no + * orphaned staircase is left clipping through the floor of the room above or below. + */ + private fun ProtectedAccess.removeLinkedStairs( + house: PohHouse, + slot: PohHotspotSlot, + removedLoc: String, + ) { + for (direction in PohStairs.linkedDirections(removedLoc)) { + var fromLoc = removedLoc + var level = slot.roomSlot.level + direction + while (true) { + val expected = PohStairs.MIRRORS[fromLoc]?.builtLoc ?: break + val linked = stairSpaceSlot(house, slot, level) ?: break + val linkedLoc = house.furniture[linked] ?: break + if (linkedLoc != expected) { + break + } + manager.removeFurniture(player, linked) + if (direction !in PohStairs.linkedDirections(linkedLoc)) { + break + } + fromLoc = linkedLoc + level += direction + } + } + } + + /** The stair-space hotspot slot of the room at [level] in [slot]'s grid column, if any. */ + private fun stairSpaceSlot(house: PohHouse, slot: PohHotspotSlot, level: Int): PohHotspotSlot? { + if (level !in PohFloor.DUNGEON.destLevel..PohFloor.UPPER.destLevel) { + return null + } + val roomSlot = PohRoomSlot(level, slot.roomSlot.gridX, slot.roomSlot.gridZ) + val room = house.rooms[roomSlot] ?: return null + val hotspot = + dataStore.hotspots(room.type).firstOrNull { it.name == PohStairs.STAIR_SPACE_NAME } + ?: return null + return PohHotspotSlot(level, roomSlot.gridX, roomSlot.gridZ, hotspot.index) + } + + /** Opens the furniture creation menu and returns the chosen row, or `null` on dismissal. */ + private suspend fun ProtectedAccess.openBuildMenu( + options: List + ): PohFurnitureRow? { + ifOpenSub( + "interface.poh_furniture_creation", + "component.toplevel_osrs_stretch:mainmodal", + IfSubType.Modal, + ) + for ((index, row) in options.withIndex()) { + val icon = PohFurnitureIcons.iconFor(row) + runClientScript( + ENTRY_CLIENTSCRIPT, + index + 1, + icon, + row.level, + menuText(row), + if (canAfford(row)) 1 else 0, + ) + } + runClientScript(FINALIZE_CLIENTSCRIPT, options.size, 0) + ifSetEvents( + "component.poh_furniture_creation:contents", + 1..options.size, + IfEvent.PauseButton, + ) + val input = coroutine.pause(ResumePauseButtonInput::class) + ifClose() + val slot = input.subcomponent + if (slot !in 1..options.size) { + return null + } + return options[slot - 1] + } + + private fun menuText(row: PohFurnitureRow): String { + val materials = buildList { + for (material in row.material) { + add("${material.t1} x ${material.t0.name}") + } + if (row.nails > 0) { + add("${row.nails} x Nails") + } + } + return "${row.menuName}|Materials: ${materials.joinToString("
")}" + } + + private fun ProtectedAccess.canAfford(row: PohFurnitureRow): Boolean = + hasMaterials(row) && (row.nails == 0 || selectNails(row.nails) != null) + + private fun ProtectedAccess.hasMaterials(row: PohFurnitureRow): Boolean = + row.material.all { invTotal(inv, it.t0.internalName) >= it.t1 } + + /** Picks the cheapest nail type the player has enough of, or `null`. */ + private fun ProtectedAccess.selectNails(count: Int): String? { + if (count == 0) { + return null + } + return NAIL_TYPES.firstOrNull { invTotal(inv, it) >= count } + } + + /** + * Drives [required] nails, consuming one per attempt with a per-metal chance of bending it + * (wasting the nail without progress). Falls through to the next nail type in the player's + * inventory when one stock runs dry; returns false when every nail is spent before the + * furniture is secured. + */ + private fun ProtectedAccess.driveNails(required: Int): Boolean { + var driven = 0 + while (driven < required) { + val type = NAIL_TYPES.firstOrNull { invTotal(inv, it) > 0 } ?: return false + invDel(inv, type, 1) + if (random.of(100) < NAIL_BEND.getValue(type)) { + mes("You accidentally bend a nail!") + } else { + driven++ + } + } + return true + } + + private fun ProtectedAccess.consumeMaterials(row: PohFurnitureRow) { + for (material in row.material) { + invDel(inv, material.t0.internalName, material.t1) + } + } + + private fun ProtectedAccess.playBuildAnim(hotspotShape: Int) { + val imcando = player.hasImcandoHammer() + val anim = + when { + hotspotShape in WALL_SHAPES -> + if (imcando) "seq.human_poh_build_wall_imcando_hammer" + else "seq.human_poh_build_wall" + hotspotShape == GROUND_DECOR_SHAPE -> + if (imcando) "seq.human_poh_build_floor_imcando_hammer" + else "seq.human_poh_build_floor" + else -> if (imcando) "seq.human_poh_build_imcando_hammer" else "seq.human_poh_build" + } + anim(anim) + } + + private fun ProtectedAccess.applyBuild( + row: PohFurnitureRow, + hotspotSlot: PohHotspotSlot, + clickedLocId: Int, + ) { + if (row.groupBuild) { + val house = manager.houseOf(player) ?: return + val roomSlot = hotspotSlot.roomSlot + val room = house.rooms[roomSlot] ?: return + val hotspotLocIds = row.hotspotLoc.map { it.id } + for (hotspot in dataStore.hotspots(room.type)) { + val locId = dev.openrune.rscm.RSCM.getRSCM(hotspot.loc) + if (locId !in hotspotLocIds) { + continue + } + val built = PohFurniture.builtLocFor(row, locId) ?: continue + val slot = + PohHotspotSlot(roomSlot.level, roomSlot.gridX, roomSlot.gridZ, hotspot.index) + manager.applyFurniture(player, slot, built.internalName) + } + } else { + val built = PohFurniture.builtLocFor(row, clickedLocId) ?: return + manager.applyFurniture(player, hotspotSlot, built.internalName) + } + } + + private fun Player.hasHammerAndSaw(): Boolean { + val hammer = invContainsAny(HAMMERS) || hasImcandoHammer() + val saw = invContainsAny(SAWS) + return hammer && saw + } + + private fun Player.hasImcandoHammer(): Boolean = invContainsAny(listOf(IMCANDO_HAMMER)) + + private fun Player.invContainsAny(types: List): Boolean { + val ids = types.map { dev.openrune.rscm.RSCM.getRSCM(it) } + val inInv = inv.any { it != null && it.id in ids } + val worn = righthand?.id in ids + return inInv || worn + } + + private companion object { + const val ENTRY_CLIENTSCRIPT = 1404 + const val FINALIZE_CLIENTSCRIPT = 1406 + + const val IMCANDO_HAMMER = "obj.imcando_hammer" + val HAMMERS = listOf("obj.hammer") + val SAWS = listOf("obj.poh_saw", "obj.eyeglo_crystal_saw") + + /** Nail types in cost order; the cheapest available stock is driven first. */ + val NAIL_TYPES = + listOf( + "obj.nails_bronze", + "obj.nails_iron", + "obj.nails_steel", + "obj.nails_black", + "obj.nails_mithril", + "obj.nails_adamant", + "obj.nails_rune", + ) + + /** + * Chance (percent) of bending a nail per drive attempt, by metal. Zenyte's approximation + * (best metals bend least); wiki-exact per-level rates are unpublished. + */ + val NAIL_BEND = + mapOf( + "obj.nails_bronze" to 60, + "obj.nails_iron" to 50, + "obj.nails_steel" to 40, + "obj.nails_black" to 30, + "obj.nails_mithril" to 20, + "obj.nails_adamant" to 10, + "obj.nails_rune" to 5, + ) + + const val REMOVE_ANIM = "seq.human_throw_away" + + val WALL_SHAPES = 0..9 + const val GROUND_DECOR_SHAPE = 22 + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/HouseOptionsScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/HouseOptionsScript.kt new file mode 100644 index 000000000..5a901214f --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/HouseOptionsScript.kt @@ -0,0 +1,84 @@ +package org.rsmod.content.skills.construction.scripts + +import jakarta.inject.Inject +import org.rsmod.api.attr.AttributeKey +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.ui.ifSetText +import org.rsmod.api.poh.PohManager +import org.rsmod.api.poh.pohDoorsOption +import org.rsmod.api.script.onIfOpen +import org.rsmod.api.script.onIfOverlayButton +import org.rsmod.content.skills.construction.scripts.HousePortalScript.Companion.armBuildingModeDropTrigger +import org.rsmod.content.skills.construction.scripts.HousePortalScript.Companion.disarmBuildingModeDropTrigger +import org.rsmod.content.skills.construction.scripts.HousePortalScript.Companion.exitHouse +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * The House Options side panel (`interface.poh_options`), opened from the settings side panel's + * house-options button (wired by `ControlSettingsScript`). + * + * `Expel guests` stays inert - guests are out of scope for this pass. + */ +class HouseOptionsScript @Inject constructor(private val manager: PohManager) : PluginScript() { + override fun ScriptContext.startup() { + onIfOpen("interface.poh_options") { player.updateRoomCount(manager) } + + onIfOverlayButton("component.poh_options:build_mode_on") { setBuildingMode(enabled = true) } + onIfOverlayButton("component.poh_options:build_mode_off") { + setBuildingMode(enabled = false) + } + + onIfOverlayButton("component.poh_options:leave_house") { + if (manager.isInOwnHouse(player)) { + exitHouse(manager) + } else { + mes("You can only do that in your own house.") + } + } + + onIfOverlayButton("component.poh_options:doors_open") { player.pohDoorsOption = 0 } + onIfOverlayButton("component.poh_options:doors_closed") { player.pohDoorsOption = 1 } + onIfOverlayButton("component.poh_options:doors_none") { player.pohDoorsOption = 2 } + + onIfOverlayButton("component.poh_options:tele_on") { player.attr[TELEPORT_INSIDE] = 1 } + onIfOverlayButton("component.poh_options:tele_off") { player.attr[TELEPORT_INSIDE] = 0 } + + onIfOverlayButton("component.poh_options:viewer") { + mes("The house viewer is not available right now.") + } + + onIfOverlayButton("component.poh_options:call_servant") { mes("You don't have a servant.") } + + onIfOverlayButton("component.poh_options:expel_guests") { + // Guests are out of scope; nothing to expel. + } + } + + private suspend fun ProtectedAccess.setBuildingMode(enabled: Boolean) { + if (!manager.isInOwnHouse(player)) { + mes("You can only do that in your own house.") + return + } + manager.setBuildingMode(player, enabled) + if (enabled) { + player.armBuildingModeDropTrigger() + mes("Building mode is now on.") + } else { + player.disarmBuildingModeDropTrigger() + mes("Building mode is now off.") + } + player.updateRoomCount(manager) + } + + private fun Player.updateRoomCount(manager: PohManager) { + val house = manager.houseOf(this) ?: return + ifSetText("component.poh_options:roomcount", "Number of rooms: ${house.rooms.size}") + } + + companion object { + /** Whether the home teleport should arrive inside the house (1) or at the portal (0). */ + val TELEPORT_INSIDE: AttributeKey = AttributeKey(persistenceKey = "poh_tele_inside") + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/HousePortalScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/HousePortalScript.kt new file mode 100644 index 000000000..45f4a2c9e --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/HousePortalScript.kt @@ -0,0 +1,99 @@ +package org.rsmod.content.skills.construction.scripts + +import jakarta.inject.Inject +import org.rsmod.api.player.output.mes +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.poh.PohManager +import org.rsmod.api.poh.pohBuildingMode +import org.rsmod.api.script.onDropTrigger +import org.rsmod.api.script.onOpLoc1 +import org.rsmod.api.script.onOpLoc2 +import org.rsmod.api.script.onOpLoc3 +import org.rsmod.api.script.onOpLoc4 +import org.rsmod.game.entity.Player +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * World house portals (`Enter` / `Home` / `Build mode` / `Friend's house`), the in-house exit + * portal, and the building-mode drop guardrail. + * + * Guests are out of scope: `Friend's house` always answers that the player is not at home. + */ +class HousePortalScript @Inject constructor(private val manager: PohManager) : PluginScript() { + override fun ScriptContext.startup() { + for (portal in WORLD_PORTALS) { + onOpLoc1(portal) { enterHouse(buildMode = false) } + onOpLoc2(portal) { enterHouse(buildMode = false) } + onOpLoc3(portal) { enterHouse(buildMode = true) } + onOpLoc4(portal) { mesbox("That player is not at home.") } + } + + onOpLoc1("loc.poh_exit_portal") { exitHouse(manager) } + + onDropTrigger(BUILDING_MODE_DROP_TRIGGER) { + player.dropTrigger(BUILDING_MODE_DROP_TRIGGER) + player.mes("You cannot drop items while in building mode.") + } + } + + private suspend fun ProtectedAccess.enterHouse(buildMode: Boolean) { + if (!manager.hasHouse(player)) { + mesbox("You don't own a house.") + return + } + when (manager.enter(player, buildMode)) { + is PohManager.EnterResult.Success -> { + if (buildMode) { + player.armBuildingModeDropTrigger() + mes("Building mode is now on.") + } else { + player.disarmBuildingModeDropTrigger() + } + } + PohManager.EnterResult.NoHouse -> mesbox("You don't own a house.") + PohManager.EnterResult.NoSpace -> + mes("Your house is currently unavailable. Please try again shortly.") + } + } + + companion object { + const val BUILDING_MODE_DROP_TRIGGER = "droptrigger.poh_building_mode" + + /** The eight verified world portal locs. Yanille's portal loc is unresolved in rev 240. */ + val WORLD_PORTALS = + listOf( + "loc.poh_taverly_portal", + "loc.poh_rimmington_portal", + "loc.poh_pollnivneach_portal", + "loc.poh_rellekka_portal", + "loc.poh_brimhaven_portal", + "loc.poh_kourend_portal", + "loc.poh_prifddinas_portal", + "loc.poh_aldarin_portal", + ) + + fun Player.armBuildingModeDropTrigger() { + if (dropTrigger == null) { + dropTrigger(BUILDING_MODE_DROP_TRIGGER) + } + } + + fun Player.disarmBuildingModeDropTrigger() { + if (dropTrigger == BUILDING_MODE_DROP_TRIGGER) { + clearDropTrigger(BUILDING_MODE_DROP_TRIGGER) + } + } + + /** Leaves the house through the exit portal, arriving at the world portal outside. */ + suspend fun ProtectedAccess.exitHouse(manager: PohManager) { + if (!manager.isInOwnHouse(player)) { + return + } + player.disarmBuildingModeDropTrigger() + player.pohBuildingMode = 0 + val coords = manager.leave(player) + telejump(coords) + } + } +} diff --git a/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/RoomScript.kt b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/RoomScript.kt new file mode 100644 index 000000000..923865446 --- /dev/null +++ b/content/skills/construction/src/main/kotlin/org/rsmod/content/skills/construction/scripts/RoomScript.kt @@ -0,0 +1,242 @@ +package org.rsmod.content.skills.construction.scripts + +import jakarta.inject.Inject +import org.rsmod.api.player.protect.ProtectedAccess +import org.rsmod.api.player.stat.baseConstructionLvl +import org.rsmod.api.poh.PohConstants +import org.rsmod.api.poh.PohDataStore +import org.rsmod.api.poh.PohDoorEdge +import org.rsmod.api.poh.PohFloor +import org.rsmod.api.poh.PohHouse +import org.rsmod.api.poh.PohManager +import org.rsmod.api.poh.PohRoom +import org.rsmod.api.poh.PohRoomSlot +import org.rsmod.api.poh.pohBuildingMode +import org.rsmod.api.script.onOpLoc5 +import org.rsmod.plugin.scripts.PluginScript +import org.rsmod.plugin.scripts.ScriptContext + +/** + * Adding and removing rooms through door hotspots in building mode. + * + * The captures never exercised `interface.poh_add_room` (212), so room selection uses the + * sanctioned chat-menu fallback. Clicking a door facing an empty grid slot offers the build list; + * clicking a door facing a built room offers its removal. + */ +class RoomScript +@Inject +constructor(private val manager: PohManager, private val dataStore: PohDataStore) : PluginScript() { + override fun ScriptContext.startup() { + val doorLocs = + dataStore.styles.flatMap { listOf(it.doorLeftLoc, it.doorRightLoc) }.distinct() + for (doorLoc in doorLocs) { + onOpLoc5(doorLoc) { onDoorBuild(it.loc.coords) } + } + } + + private suspend fun ProtectedAccess.onDoorBuild(coords: org.rsmod.map.CoordGrid) { + if (player.pohBuildingMode != 1) { + mes("You can only do that in building mode.") + return + } + val house = manager.houseOf(player) ?: return + val (slot, edge) = manager.findDoorEdge(player, coords) ?: return + val target = slot.translate(edge) + val existing = house.rooms[target] + if (existing != null) { + // Shared doorways keep a single clickable face, so removal targets the room on the + // far side of wherever the player stands - both rooms stay removable either way. + val region = manager.activeHouse(player)?.region + val playerSlot = region?.let { manager.roomSlotAt(it, player.coords) } + removeRoom(house, if (playerSlot == target) slot else target) + } else { + addRoom(house, target, edge) + } + } + + private suspend fun ProtectedAccess.addRoom( + house: PohHouse, + target: PohRoomSlot, + edge: PohDoorEdge, + ) { + if (!house.inBuildableArea(target)) { + mes("Your house isn't big enough for that.") + return + } + if (house.rooms.size >= PohConstants.maxRooms(player.baseConstructionLvl)) { + mes("You can't build any more rooms at your Construction level.") + return + } + if (target.level == PohFloor.UPPER.destLevel) { + val below = PohRoomSlot(PohFloor.GROUND.destLevel, target.gridX, target.gridZ) + val support = house.rooms[below] + if (support == null || support.type in OPEN_AIR_ROOMS) { + mes("You can't build a room up there with nothing to support it.") + return + } + } + + val options = ROOM_OPTIONS.filter { target.level in it.floors } + if (options.isEmpty()) { + mes("You can't build a room there.") + return + } + val labels = options.map { "${it.label} (${it.cost} coins) - level ${it.level}" } + val index = menu("Select a room", hotkeys = false, labels) + val option = options.getOrNull(index) ?: return + + if (player.baseConstructionLvl < option.level) { + mes("You need a Construction level of ${option.level} to build that room.") + return + } + if (invTotal(inv, COINS) < option.cost) { + mes("You need ${option.cost} coins to build that room.") + return + } + + val roomKey = option.roomKeyFor(target.level) + val rotation = alignRotation(roomKey, edge) + if (rotation == null) { + mes("The room's doors don't line up.") + return + } + + val confirm = + choice2( + "Yes.", + true, + "No.", + false, + title = "Build ${option.label} for ${option.cost} coins?", + ) + if (!confirm) { + return + } + + invDel(inv, COINS, option.cost) + manager.addRoom(player, target, PohRoom(roomKey, rotation)) + mes("You build a ${option.label.lowercase()}.") + } + + private suspend fun ProtectedAccess.removeRoom(house: PohHouse, target: PohRoomSlot) { + if (target.level == PohFloor.GROUND.destLevel) { + val above = PohRoomSlot(PohFloor.UPPER.destLevel, target.gridX, target.gridZ) + if (house.rooms.containsKey(above)) { + mes("You can't remove a room that has a room above it.") + return + } + } + // The house must always keep a way out: the removed room can't take the only built exit + // portal, nor the last garden the automatic exit-portal fallback could be placed in. + val exitElsewhere = + house.furniture.any { (slot, loc) -> + loc == PohConstants.EXIT_PORTAL_LOC && slot.roomSlot != target + } + val gardenElsewhere = + house.rooms.any { (slot, room) -> slot != target && room.type in EXIT_GARDEN_ROOMS } + if (!exitElsewhere && !gardenElsewhere) { + mes("Your house must have at least one exit portal.") + return + } + val confirm = choice2("Yes.", true, "No.", false, title = "Really remove it?") + if (!confirm) { + return + } + manager.removeRoom(player, target) + mes("You remove the room.") + } + + /** + * Picks the first rotation that turns one of the room's door edges back toward the room the + * player is building from, or `null` when no rotation lines the doors up. + */ + private fun alignRotation(roomKey: String, edge: PohDoorEdge): Int? { + val type = dataStore.room(roomKey) + if (type.doorEdges.isEmpty()) { + return null + } + for (rotation in 0..3) { + if (type.doorEdges.any { it.rotate(rotation) == edge.opposite }) { + return rotation + } + } + return null + } + + private data class RoomOption( + val label: String, + val roomKey: String, + val level: Int, + val cost: Int, + val floors: Set, + /** Upstairs template variant for the stairs-linked halls. */ + val upperRoomKey: String = roomKey, + ) { + fun roomKeyFor(destLevel: Int): String = + if (destLevel == PohFloor.UPPER.destLevel) upperRoomKey else roomKey + } + + private companion object { + + /** + * Rooms the automatic exit-portal fallback can host a portal in (PohManager.findGarden). + */ + val EXIT_GARDEN_ROOMS = setOf("garden", "superior_garden") + const val COINS = "obj.coins" + + /** + * Open-air ground rooms that can't support an upstairs room (wiki: no roof to build on). + */ + val OPEN_AIR_ROOMS = + setOf("garden", "formal_garden", "superior_garden", "menagerie_outdoor") + + val GROUND_FLOORS = setOf(PohFloor.GROUND.destLevel, PohFloor.UPPER.destLevel) + val GROUND_ONLY = setOf(PohFloor.GROUND.destLevel) + val DUNGEON_ONLY = setOf(PohFloor.DUNGEON.destLevel) + + /** Room list with the wiki level and cost gates. */ + val ROOM_OPTIONS = + listOf( + RoomOption("Parlour", "parlour", 1, 1_000, GROUND_FLOORS), + RoomOption("Garden", "garden", 1, 1_000, GROUND_ONLY), + RoomOption("Kitchen", "kitchen", 5, 5_000, GROUND_FLOORS), + RoomOption("Dining room", "dining_room", 10, 5_000, GROUND_FLOORS), + RoomOption("Workshop", "workshop", 15, 10_000, GROUND_FLOORS), + RoomOption("Bedroom", "bedroom", 20, 10_000, GROUND_FLOORS), + RoomOption( + "Skill hall", + "skill_hall_stairs_up", + 25, + 15_000, + GROUND_FLOORS, + upperRoomKey = "skill_hall_stairs_top", + ), + RoomOption("Games room", "games_room", 30, 25_000, GROUND_FLOORS), + RoomOption("Combat room", "combat_room", 32, 25_000, GROUND_FLOORS), + RoomOption( + "Quest hall", + "quest_hall_stairs_up", + 35, + 25_000, + GROUND_FLOORS, + upperRoomKey = "quest_hall_stairs_top", + ), + RoomOption("Menagerie", "menagerie_outdoor", 37, 30_000, GROUND_ONLY), + RoomOption("Menagerie (indoors)", "menagerie_indoor", 37, 30_000, GROUND_ONLY), + RoomOption("Study", "study", 40, 50_000, GROUND_FLOORS), + RoomOption("Costume room", "costume_room", 42, 50_000, GROUND_FLOORS), + RoomOption("Chapel", "chapel", 45, 50_000, GROUND_FLOORS), + RoomOption("Portal chamber", "portal_chamber", 50, 100_000, GROUND_FLOORS), + RoomOption("Formal garden", "formal_garden", 55, 75_000, GROUND_ONLY), + RoomOption("Throne room", "throne_room", 60, 150_000, GROUND_ONLY), + RoomOption("Superior garden", "superior_garden", 65, 75_000, GROUND_ONLY), + RoomOption("Oubliette", "oubliette", 65, 150_000, DUNGEON_ONLY), + RoomOption("Dungeon corridor", "dungeon_corridor", 70, 7_500, DUNGEON_ONLY), + RoomOption("Dungeon junction", "dungeon_junction", 70, 7_500, DUNGEON_ONLY), + RoomOption("Dungeon stairs", "dungeon_stairs", 70, 7_500, DUNGEON_ONLY), + RoomOption("Portal nexus", "portal_nexus", 72, 200_000, GROUND_FLOORS), + RoomOption("Treasure room", "treasure_room", 75, 250_000, DUNGEON_ONLY), + RoomOption("Achievement gallery", "achievement_gallery", 80, 200_000, GROUND_FLOORS), + ) + } +} diff --git a/content/skills/construction/src/main/resources/gamevals.toml b/content/skills/construction/src/main/resources/gamevals.toml new file mode 100644 index 000000000..706cdb465 --- /dev/null +++ b/content/skills/construction/src/main/resources/gamevals.toml @@ -0,0 +1,494 @@ +[gamevals.dbtable] +poh_furniture =65506 + +[gamevals.dbrow] +# Parlour +poh_crude_wooden_chair =64130 +poh_wooden_chair =64129 +poh_rocking_chair =64128 +poh_oak_chair =64127 +poh_oak_armchair =64126 +poh_teak_armchair =64125 +poh_mahogany_armchair =64124 +poh_wooden_bookcase =64123 +poh_oak_bookcase =64122 +poh_mahogany_bookcase =64121 +poh_clay_fireplace =64120 +poh_stone_fireplace =64119 +poh_marble_fireplace =64118 +poh_torn_curtains =64117 +poh_curtains =64116 +poh_opulent_curtains =64115 +poh_brown_rug =64114 +poh_rug =64113 +poh_opulent_rug =64112 +# Kitchen +poh_firepit =64111 +poh_firepit_with_hook =64110 +poh_firepit_with_pot =64109 +poh_small_oven =64108 +poh_large_oven =64107 +poh_steel_range =64106 +poh_fancy_range =64105 +poh_wooden_shelves_1 =64104 +poh_wooden_shelves_2 =64103 +poh_wooden_shelves_3 =64102 +poh_oak_shelves_1 =64101 +poh_oak_shelves_2 =64100 +poh_teak_shelves_1 =64099 +poh_teak_shelves_2 =64098 +poh_pump_and_drain =64097 +poh_pump_and_tub =64096 +poh_kitchen_sink =64095 +poh_gold_sink =64094 +poh_wooden_larder =64093 +poh_oak_larder =64092 +poh_teak_larder =64091 +poh_beer_barrel =64090 +poh_cider_barrel =64089 +poh_asgarnian_ale_barrel =64088 +poh_greenmans_ale_barrel =64087 +poh_dragon_bitter_barrel =64086 +poh_chefs_delight_barrel =64085 +poh_cat_blanket =64084 +poh_cat_basket =64083 +poh_cushioned_basket =64082 +poh_kitchen_table =64081 +poh_oak_kitchen_table =64080 +poh_teak_kitchen_table =64079 +poh_spice_rack =64078 +# Dining room +poh_wood_dining_table =64077 +poh_oak_dining_table =64076 +poh_carved_oak_table =64075 +poh_teak_table =64074 +poh_carved_teak_table =64073 +poh_mahogany_table =64072 +poh_opulent_table =64071 +poh_wooden_bench =64070 +poh_wooden_bench_2 =64069 +poh_oak_bench =64068 +poh_oak_bench_2 =64067 +poh_carved_oak_bench =64066 +poh_carved_oak_bench_2 =64065 +poh_teak_dining_bench =64064 +poh_teak_dining_bench_2 =64063 +poh_carved_teak_bench =64062 +poh_carved_teak_bench_2 =64061 +poh_mahogany_bench =64060 +poh_mahogany_bench_2 =64059 +poh_gilded_bench =64058 +poh_gilded_bench_2 =64057 +poh_rope_bellpull =64056 +poh_bellpull =64055 +poh_posh_bellpull =64054 +poh_greenman_carving =64053 +poh_oak_wall_decoration =64052 +poh_teak_wall_decoration =64051 +poh_gilded_decoration =64050 +# Bedroom +poh_wooden_bed =64049 +poh_oak_bed =64048 +poh_large_oak_bed =64047 +poh_teak_bed =64046 +poh_large_teak_bed =64045 +poh_four_poster =64044 +poh_gilded_four_poster =64043 +poh_shoe_box =64042 +poh_oak_drawers =64041 +poh_oak_wardrobe =64040 +poh_teak_drawers =64039 +poh_teak_wardrobe =64038 +poh_mahogany_wardrobe =64037 +poh_gilded_wardrobe =64036 +poh_shaving_stand =64035 +poh_oak_shaving_stand =64034 +poh_oak_dresser =64033 +poh_teak_dresser =64032 +poh_fancy_teak_dresser =64031 +poh_mahogany_dresser =64030 +poh_gilded_dresser =64029 +poh_oak_clock =64028 +poh_teak_clock =64027 +poh_servants_moneybag =64026 +poh_gilded_clock =64025 +# Workshop +poh_wooden_workbench =64024 +poh_oak_workbench =64023 +poh_steel_framed_workbench =64022 +poh_bench_with_vice =64021 +poh_bench_with_lathe =64020 +poh_workshop_tool_store_1 =64019 +poh_workshop_tool_store_2 =64018 +poh_workshop_tool_store_3 =64017 +poh_workshop_tool_store_4 =64016 +poh_workshop_tool_store_5 =64015 +poh_workshop_crafting_table_1 =64014 +poh_workshop_crafting_table_2 =64013 +poh_workshop_crafting_table_3 =64012 +poh_workshop_crafting_table_4 =64011 +poh_repair_bench =64010 +poh_whetstone =64009 +poh_armour_stand =64008 +poh_pluming_stand =64007 +poh_shield_easel =64006 +poh_banner_easel =64005 +# Study +poh_oak_lectern =64004 +poh_eagle_lectern =64003 +poh_demon_lectern =64002 +poh_teak_eagle_lectern =64001 +poh_teak_demon_lectern =64000 +poh_mahogany_eagle_lectern =63999 +poh_mahogany_demon_lectern =63998 +poh_marble_lectern =63997 +poh_globe =63996 +poh_ornamental_globe =63995 +poh_lunar_globe =63994 +poh_celestial_globe =63993 +poh_armillary_sphere =63992 +poh_small_orrery =63991 +poh_large_orrery =63990 +poh_crystal_ball =63989 +poh_elemental_sphere =63988 +poh_crystal_of_power =63987 +poh_oak_telescope =63986 +poh_teak_telescope =63985 +poh_mahogany_telescope =63984 +poh_alchemical_chart =63983 +poh_astronomical_chart =63982 +poh_infernal_chart =63981 +# Chapel +poh_oak_altar =63980 +poh_teak_altar =63979 +poh_cloth_altar =63978 +poh_mahogany_altar =63977 +poh_limestone_altar =63976 +poh_marble_altar =63975 +poh_gilded_altar =63974 +poh_steel_torches =63973 +poh_wooden_torches =63972 +poh_steel_candlesticks =63971 +poh_gold_candlesticks =63970 +poh_oak_incense_burners =63969 +poh_mahogany_incense_burners =63968 +poh_marble_incense_burners =63967 +poh_saradomin_symbol =63966 +poh_zamorak_symbol =63965 +poh_guthix_symbol =63964 +poh_saradomin_icon =63963 +poh_zamorak_icon =63962 +poh_guthix_icon =63961 +poh_icon_of_bob =63960 +poh_chapel_windchimes =63959 +poh_chapel_bells =63958 +poh_chapel_organ =63957 +poh_small_statue =63956 +poh_medium_statue =63955 +poh_large_statue =63954 +poh_shuttered_window =63953 +poh_decorative_window =63952 +poh_stained_glass =63951 +# Throne room +poh_oak_throne =63950 +poh_teak_throne =63949 +poh_mahogany_throne =63948 +poh_gilded_throne =63947 +poh_skeleton_throne =63946 +poh_crystal_throne =63945 +poh_demonic_throne =63944 +poh_oak_lever =63943 +poh_teak_lever =63942 +poh_mahogany_lever =63941 +poh_oak_trapdoor =63940 +poh_teak_trapdoor =63939 +poh_mahogany_trapdoor =63938 +poh_floor_decoration =63937 +poh_throne_steel_cage =63936 +poh_trapdoor_floor =63935 +poh_lesser_magic_cage =63934 +poh_greater_magic_cage =63933 +poh_throne_carved_teak_bench =63932 +poh_throne_carved_teak_bench_2 =63931 +poh_throne_mahogany_bench =63930 +poh_throne_mahogany_bench_2 =63929 +poh_throne_gilded_bench =63928 +poh_throne_gilded_bench_2 =63927 +poh_round_shield =63926 +poh_square_shield =63925 +poh_kite_shield =63924 +# Games room +poh_lesser_magical_balance =63923 +poh_medium_magical_balance =63922 +poh_greater_magical_balance =63921 +poh_jester =63920 +poh_treasure_hunt =63919 +poh_hangman =63918 +poh_oak_prize_chest =63917 +poh_teak_prize_chest =63916 +poh_mahogany_prize_chest =63915 +poh_hoop_and_stick =63914 +poh_dartboard =63913 +poh_archery_target =63912 +poh_clay_attack_stone =63911 +poh_limestone_attack_stone =63910 +poh_marble_attack_stone =63909 +# Combat room +poh_boxing_ring =63908 +poh_fencing_ring =63907 +poh_combat_ring =63906 +poh_ranging_pedestals =63905 +poh_boxing_glove_rack =63904 +poh_weapons_rack =63903 +poh_extra_weapons_rack =63902 +poh_combat_dummy_row =63901 +poh_undead_combat_dummy =63900 +poh_ornate_undead_combat_dummy =63899 +# Portal chamber +poh_teak_portal =63898 +poh_mahogany_portal =63897 +poh_marble_portal =63896 +poh_teleport_focus =63895 +poh_greater_teleport_focus =63894 +poh_scrying_pool =63893 +# Costume room +poh_oak_armour_case =63892 +poh_teak_armour_case =63891 +poh_mahogany_armour_case =63890 +poh_oak_cape_rack =63889 +poh_teak_cape_rack =63888 +poh_mahogany_cape_rack =63887 +poh_gilded_cape_rack =63886 +poh_marble_cape_rack =63885 +poh_magical_cape_rack =63884 +poh_oak_fancy_dress_box =63883 +poh_teak_fancy_dress_box =63882 +poh_mahogany_fancy_dress_box =63881 +poh_oak_magic_wardrobe =63880 +poh_carved_oak_magic_wardrobe =63879 +poh_teak_magic_wardrobe =63878 +poh_carved_teak_magic_wardrobe =63877 +poh_mahogany_magic_wardrobe =63876 +poh_gilded_magic_wardrobe =63875 +poh_marble_magic_wardrobe =63874 +poh_oak_toy_box =63873 +poh_teak_toy_box =63872 +poh_mahogany_toy_box =63871 +poh_oak_treasure_chest =63870 +poh_teak_treasure_chest =63869 +poh_mahogany_treasure_chest =63868 +# Menagerie +poh_simple_arena =63867 +poh_advanced_arena =63866 +poh_glorious_arena =63865 +poh_grassland_habitat =63864 +poh_forest_habitat =63863 +poh_desert_habitat =63862 +poh_polar_habitat =63861 +poh_volcanic_habitat =63860 +poh_oak_feeder =63859 +poh_teak_feeder =63858 +poh_mahogany_feeder =63857 +poh_oak_pet_house =63856 +poh_teak_pet_house =63855 +poh_mahogany_pet_house =63854 +poh_consecrated_pet_house =63853 +poh_desecrated_pet_house =63852 +poh_nature_pet_house =63851 +poh_pet_list =63850 +poh_oak_scratching_post =63849 +poh_teak_scratching_post =63848 +poh_mahogany_scratching_post =63847 +# Dungeon (shared) +poh_dungeon_skeleton_guard =63846 +poh_dungeon_guard_dog =63845 +poh_hobgoblin_guard =63844 +poh_baby_red_dragon =63843 +poh_huge_spider =63842 +poh_troll_guard =63841 +poh_hellhound_guard =63840 +poh_dungeon_spike_trap =63839 +poh_dungeon_man_trap =63838 +poh_tangle_vine =63837 +poh_dungeon_marble_trap =63836 +poh_dungeon_teleport_trap =63835 +poh_oak_door =63834 +poh_oak_door_2 =63833 +poh_steel_plated_door =63832 +poh_steel_plated_door_2 =63831 +poh_marble_door =63830 +poh_marble_door_2 =63829 +poh_dungeon_candles =63828 +poh_dungeon_torches =63827 +poh_dungeon_skull_torches =63826 +poh_decorative_blood =63825 +poh_decorative_pipe =63824 +poh_hanging_skeleton =63823 +# Oubliette +poh_oubliette_floor_spikes =63822 +poh_tentacle_pool =63821 +poh_flame_pit =63820 +poh_rocnar =63819 +poh_oak_cage =63818 +poh_oak_and_steel_cage =63817 +poh_oubliette_steel_cage =63816 +poh_spiked_cage =63815 +poh_bone_cage =63814 +poh_oak_ladder =63813 +poh_teak_ladder =63812 +poh_mahogany_ladder =63811 +# Treasure room +poh_wooden_crate =63810 +poh_oak_chest =63809 +poh_teak_chest =63808 +poh_mahogany_chest =63807 +poh_magic_chest =63806 +poh_treasure_demon =63805 +poh_treasure_kalphite_soldier =63804 +poh_treasure_tok_xil =63803 +poh_treasure_dagannoth =63802 +poh_treasure_steel_dragon =63801 +poh_treasure_rune_dragon =63800 +# Skill hall & Quest hall +poh_hall_rug =63799 +poh_hall_opulent_rug =63798 +poh_oak_staircase =63797 +poh_teak_staircase =63796 +poh_limestone_spiral_staircase =63795 +poh_marble_staircase =63794 +poh_marble_spiral =63793 +poh_teak_head_display =63792 +poh_mahogany_head_display =63791 +poh_gilded_head_display =63790 +poh_oak_fish_display =63789 +poh_teak_fish_display =63788 +poh_mahogany_fish_display =63787 +poh_mithril_armour =63786 +poh_adamantite_armour =63785 +poh_runite_armour =63784 +poh_red_castlewars_armour =63783 +poh_white_castlewars_armour =63782 +poh_gold_castlewars_armour =63781 +poh_rune_case_1 =63780 +poh_rune_case_2 =63779 +poh_rune_case_3 =63778 +poh_king_arthur_portrait =63777 +poh_elena_portrait =63776 +poh_giant_dwarf_portrait =63775 +poh_miscellanians_portrait =63774 +poh_landscape_lumbridge =63773 +poh_landscape_desert =63772 +poh_landscape_morytania =63771 +poh_landscape_karamja =63770 +poh_landscape_isafdar =63769 +poh_mounted_anti_dragon_shield =63768 +poh_mounted_amulet_of_glory =63767 +poh_mounted_cape_of_legends =63766 +poh_mounted_mythical_cape =63765 +poh_mounted_silverlight =63764 +poh_mounted_excalibur =63763 +poh_mounted_darklight =63762 +poh_small_map =63761 +poh_medium_map =63760 +poh_large_map =63759 +# Garden +poh_exit_portal_build =63758 +poh_garden_greenman_statue =63757 +poh_decorative_rock =63756 +poh_pond =63755 +poh_imp_statue =63754 +poh_dungeon_entrance =63753 +poh_tip_jar =63752 +poh_garden_dead_tree =63751 +poh_garden_nice_tree =63750 +poh_garden_oak_tree =63749 +poh_garden_willow_tree =63748 +poh_garden_maple_tree =63747 +poh_garden_yew_tree =63746 +poh_garden_magic_tree =63745 +poh_garden_fern =63744 +poh_garden_bush =63743 +poh_garden_tall_plant =63742 +poh_garden_short_plant =63741 +poh_garden_large_leaf_bush =63740 +poh_garden_huge_plant =63739 +poh_garden_plant =63738 +poh_garden_small_fern =63737 +poh_garden_fern_small =63736 +poh_garden_dock_leaf =63735 +poh_garden_thistle =63734 +poh_garden_reeds =63733 +# Formal garden +poh_gazebo =63732 +poh_small_fountain =63731 +poh_large_fountain =63730 +poh_posh_fountain =63729 +poh_boundary_stones =63728 +poh_wooden_fence =63727 +poh_stone_wall =63726 +poh_iron_railings =63725 +poh_picket_fence =63724 +poh_garden_fence =63723 +poh_marble_wall =63722 +poh_thorny_hedge =63721 +poh_nice_hedge =63720 +poh_small_box_hedge =63719 +poh_topiary_hedge =63718 +poh_fancy_hedge =63717 +poh_tall_fancy_hedge =63716 +poh_tall_box_hedge =63715 +poh_sunflower =63714 +poh_marigolds =63713 +poh_roses =63712 +poh_rosemary =63711 +poh_daffodils =63710 +poh_bluebells =63709 +# Superior garden +poh_restoration_pool =63708 +poh_revitalisation_pool =63707 +poh_rejuvenation_pool =63706 +poh_fancy_rejuvenation_pool =63705 +poh_ornate_rejuvenation_pool =63704 +poh_spirit_tree_teleport =63703 +poh_obelisk =63702 +poh_fairy_ring_teleport =63701 +poh_spiritual_fairy_tree =63700 +poh_zen_theme =63699 +poh_otherworldly_theme =63698 +poh_volcanic_theme =63697 +poh_topiary_bush =63696 +poh_redwood_fence =63695 +poh_superior_marble_wall =63694 +poh_obsidian_fence =63693 +poh_teak_garden_bench =63692 +poh_teak_garden_bench_2 =63691 +poh_gnome_bench =63690 +poh_gnome_bench_2 =63689 +poh_marble_bench =63688 +poh_marble_bench_2 =63687 +poh_obsidian_bench =63686 +poh_obsidian_bench_2 =63685 +# Achievement gallery +poh_ancient_altar =63684 +poh_lunar_altar =63683 +poh_dark_altar =63682 +poh_occult_altar =63681 +poh_occult_altar_2 =63680 +poh_occult_altar_3 =63679 +poh_mahogany_adventure_log =63678 +poh_gilded_adventure_log =63677 +poh_marble_adventure_log =63676 +poh_boss_lair_display =63675 +poh_mounted_emblem =63674 +poh_mounted_coins =63673 +poh_cape_hanger =63672 +poh_basic_jewellery_box =63671 +poh_fancy_jewellery_box =63670 +poh_ornate_jewellery_box =63669 +poh_quest_list =63668 +# Portal nexus +poh_marble_portal_nexus =63667 +poh_gilded_portal_nexus =63666 +poh_crystalline_portal_nexus =63665 +poh_mounted_xerics_talisman =63664 +poh_mounted_digsite_pendant =63663 \ No newline at end of file diff --git a/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/GildedAltarEvents.kt b/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/GildedAltarEvents.kt index 27dcf15aa..cd2e4c4f3 100644 --- a/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/GildedAltarEvents.kt +++ b/content/skills/prayer/src/main/kotlin/org/rsmod/content/skills/prayer/GildedAltarEvents.kt @@ -21,9 +21,6 @@ class GildedAltarEvents @Inject constructor( bones.forEach { row -> registerAltar("loc.chaosaltar", row, chaos = true) - GILDED_ALTARS.forEach { altar -> - registerAltar(altar, row, chaos = false) - } } onPlayerQueueWithArgs("queue.prayer_altar_sacrifice") { @@ -107,12 +104,4 @@ class GildedAltarEvents @Inject constructor( val altar: BoundLocInfo, val chaos: Boolean, ) - - private companion object { - val GILDED_ALTARS = listOf( - "loc.poh_altar_saradomin_7", - "loc.poh_altar_zamorak_7", - "loc.poh_altar_gnomechild_7", - ) - } } diff --git a/or-cache/src/main/kotlin/dev/openrune/CacheTools.kt b/or-cache/src/main/kotlin/dev/openrune/CacheTools.kt index 553ddccc4..79b1bbc7f 100644 --- a/or-cache/src/main/kotlin/dev/openrune/CacheTools.kt +++ b/or-cache/src/main/kotlin/dev/openrune/CacheTools.kt @@ -36,6 +36,7 @@ import dev.openrune.tables.consumables.food.FoodTable import dev.openrune.tables.consumables.potion.PotionEffectTable import dev.openrune.tables.consumables.potion.PotionTable import dev.openrune.tables.skills.Cooking +import dev.openrune.tables.skills.Construction import dev.openrune.tables.skills.Firemaking import dev.openrune.tables.skills.Herblore import dev.openrune.tables.skills.Mining @@ -177,6 +178,7 @@ fun tablesToPack(): List = listOf( StatComponents.statsComponents(), PickableObjects.pickableObjects(), Mining.rocks(), + Construction.furniture(), Cooking.foods(), Cooking.ales(), Herblore.unfinishedPotions(), diff --git a/or-cache/src/main/kotlin/dev/openrune/tables/skills/Construction.kt b/or-cache/src/main/kotlin/dev/openrune/tables/skills/Construction.kt new file mode 100644 index 000000000..dc67ecde5 --- /dev/null +++ b/or-cache/src/main/kotlin/dev/openrune/tables/skills/Construction.kt @@ -0,0 +1,6683 @@ +package dev.openrune.tables.skills + +import dev.openrune.definition.dbtables.DBRowBuilder +import dev.openrune.definition.dbtables.dbTable +import dev.openrune.definition.util.VarType + +/** + * `dbtable.poh_furniture`: one row per Construction build option. + * + * - [COL_HOTSPOT_LOC] / [COL_BUILT_LOC] are parallel lists: building this option on the i-th + * hotspot loc yields the i-th built loc. Options offered on several independent hotspots (e.g. + * the three parlour chair spaces) list each hotspot with the same built loc; multi-piece + * furniture (rugs, stairs) lists each piece's hotspot with its matching piece loc. + * - [COL_GROUP_BUILD] `true` means one build op replaces *every* placement of the listed hotspot + * locs in the room at once (rugs, curtains); `false` replaces only the clicked placement. + * - [COL_XP] uses fine precision (x10), matching `dbtable.mining_rocks`. + * - [COL_NAILS] is the number of nails required; any nail type is accepted at build time. + * - Upgrade tiers (workbenches, tool stores, crafting tables, pools, jewellery boxes, costume-room + * storage, pet houses, portal nexus, occult altar, combat dummies) list the previous tier's + * BUILT loc as their hotspot; those locs carry an `Upgrade` op instead of `Build`. + * + * Data source: OSRS wiki room pages (level / materials / experience tables). + */ +object Construction { + + const val COL_HOTSPOT_LOC = 0 + const val COL_BUILT_LOC = 1 + const val COL_LEVEL = 2 + const val COL_XP = 3 + const val COL_MATERIAL = 4 + const val COL_NAILS = 5 + const val COL_MENU_NAME = 6 + const val COL_GROUP_BUILD = 7 + const val COL_FLATPACK = 8 + + /** Adds a `material` obj+count pair; call repeatedly for multi-material options. */ + private class MaterialList { + val objs = mutableListOf() + val counts = mutableListOf() + + fun material(obj: String, count: Int) { + objs += obj + counts += count + } + } + + private fun DBRowBuilder.option( + name: String, + level: Int, + xp: Double, + hotspots: List, + built: List, + groupBuild: Boolean = false, + nails: Int = 0, + flatpack: String? = null, + materials: MaterialList.() -> Unit, + ) { + require(hotspots.size == built.size) { + "hotspot/built lists must align for option '$name'" + } + columnRSCM(COL_HOTSPOT_LOC, *hotspots.toTypedArray()) + columnRSCM(COL_BUILT_LOC, *built.toTypedArray()) + column(COL_LEVEL, level) + column(COL_XP, (xp * 10).toInt()) + val mats = MaterialList().apply(materials) + if (mats.objs.isNotEmpty()) { + materialColumn(COL_MATERIAL, mats) + } + column(COL_NAILS, nails) + column(COL_MENU_NAME, name) + column(COL_GROUP_BUILD, groupBuild) + if (flatpack != null) { + columnRSCM(COL_FLATPACK, flatpack) + } + } + + private fun DBRowBuilder.materialColumn(index: Int, mats: MaterialList) { + val values = mutableListOf() + for (i in mats.objs.indices) { + values += dev.openrune.definition.constants.ConstantProvider.getMapping(mats.objs[i]) + values += mats.counts[i] + } + column(index, *values.toTypedArray()) + } + + private val PARLOUR_CHAIR_SPACES = + listOf("loc.poh_parlour_1", "loc.poh_parlour_2", "loc.poh_parlour_3") + + private fun chairBuilt(chair: String) = List(PARLOUR_CHAIR_SPACES.size) { chair } + + fun furniture() = + dbTable("dbtable.poh_furniture", serverOnly = true) { + column("hotspot_loc", COL_HOTSPOT_LOC, VarType.LOC) + column("built_loc", COL_BUILT_LOC, VarType.LOC) + column("level", COL_LEVEL, VarType.INT) + column("xp", COL_XP, VarType.INT) + column("material", COL_MATERIAL, VarType.OBJ, VarType.INT) + column("nails", COL_NAILS, VarType.INT) + column("menu_name", COL_MENU_NAME, VarType.STRING) + column("group_build", COL_GROUP_BUILD, VarType.BOOLEAN) + column("flatpack", COL_FLATPACK, VarType.OBJ) + + /* Parlour - Chairs (three independent chair spaces). */ + row("dbrow.poh_crude_wooden_chair") { + option( + "Crude wooden chair", + level = 1, + xp = 58.0, + hotspots = PARLOUR_CHAIR_SPACES, + built = chairBuilt("loc.poh_chair1"), + nails = 2, + flatpack = "obj.poh_flatpack_armchair1", + ) { + material("obj.woodplank", 2) + } + } + row("dbrow.poh_wooden_chair") { + option( + "Wooden chair", + level = 8, + xp = 87.0, + hotspots = PARLOUR_CHAIR_SPACES, + built = chairBuilt("loc.poh_chair2"), + nails = 3, + flatpack = "obj.poh_flatpack_armchair2", + ) { + material("obj.woodplank", 3) + } + } + row("dbrow.poh_rocking_chair") { + option( + "Rocking chair", + level = 14, + xp = 87.0, + hotspots = PARLOUR_CHAIR_SPACES, + built = chairBuilt("loc.poh_chair3"), + nails = 3, + flatpack = "obj.poh_flatpack_armchair3", + ) { + material("obj.woodplank", 3) + } + } + row("dbrow.poh_oak_chair") { + option( + "Oak chair", + level = 19, + xp = 120.0, + hotspots = PARLOUR_CHAIR_SPACES, + built = chairBuilt("loc.poh_chair4"), + flatpack = "obj.poh_flatpack_armchair4", + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_oak_armchair") { + option( + "Oak armchair", + level = 26, + xp = 180.0, + hotspots = PARLOUR_CHAIR_SPACES, + built = chairBuilt("loc.poh_chair5"), + flatpack = "obj.poh_flatpack_armchair5", + ) { + material("obj.plank_oak", 3) + } + } + row("dbrow.poh_teak_armchair") { + option( + "Teak armchair", + level = 35, + xp = 180.0, + hotspots = PARLOUR_CHAIR_SPACES, + built = chairBuilt("loc.poh_chair6"), + flatpack = "obj.poh_flatpack_armchair6", + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_mahogany_armchair") { + option( + "Mahogany armchair", + level = 50, + xp = 280.0, + hotspots = PARLOUR_CHAIR_SPACES, + built = chairBuilt("loc.poh_chair7"), + flatpack = "obj.poh_flatpack_armchair7", + ) { + material("obj.plank_mahogany", 2) + } + } + + /* Bookcases - shared ladder for the parlour, study, and quest hall spaces. */ + row("dbrow.poh_wooden_bookcase") { + option( + "Wooden bookcase", + level = 4, + xp = 115.0, + hotspots = listOf("loc.poh_parlour_5", "loc.poh_study_7", "loc.poh_hall2_7"), + built = listOf("loc.poh_bookcase1", "loc.poh_bookcase1", "loc.poh_bookcase1"), + nails = 4, + ) { + material("obj.woodplank", 4) + } + } + row("dbrow.poh_oak_bookcase") { + option( + "Oak bookcase", + level = 29, + xp = 180.0, + hotspots = listOf("loc.poh_parlour_5", "loc.poh_study_7", "loc.poh_hall2_7"), + built = listOf("loc.poh_bookcase2", "loc.poh_bookcase2", "loc.poh_bookcase2"), + ) { + material("obj.plank_oak", 3) + } + } + row("dbrow.poh_mahogany_bookcase") { + option( + "Mahogany bookcase", + level = 40, + xp = 420.0, + hotspots = listOf("loc.poh_parlour_5", "loc.poh_study_7", "loc.poh_hall2_7"), + built = listOf("loc.poh_bookcase3", "loc.poh_bookcase3", "loc.poh_bookcase3"), + ) { + material("obj.plank_mahogany", 3) + } + } + + /* Fireplaces - shared ladder for the parlour, dining room, and bedroom. */ + row("dbrow.poh_clay_fireplace") { + option( + "Clay fireplace", + level = 3, + xp = 30.0, + hotspots = + listOf( + "loc.poh_parlour_6", + "loc.poh_dining_room_4", + "loc.poh_bedroom_6", + ), + built = + listOf( + "loc.poh_fireplace_1", + "loc.poh_fireplace_1", + "loc.poh_fireplace_1", + ), + ) { + material("obj.softclay", 3) + } + } + row("dbrow.poh_stone_fireplace") { + option( + "Stone fireplace", + level = 33, + xp = 40.0, + hotspots = + listOf( + "loc.poh_parlour_6", + "loc.poh_dining_room_4", + "loc.poh_bedroom_6", + ), + built = + listOf( + "loc.poh_fireplace_2", + "loc.poh_fireplace_2", + "loc.poh_fireplace_2", + ), + ) { + material("obj.limestonebrick", 2) + } + } + row("dbrow.poh_marble_fireplace") { + option( + "Marble fireplace", + level = 63, + xp = 500.0, + hotspots = + listOf( + "loc.poh_parlour_6", + "loc.poh_dining_room_4", + "loc.poh_bedroom_6", + ), + built = + listOf( + "loc.poh_fireplace_3", + "loc.poh_fireplace_3", + "loc.poh_fireplace_3", + ), + ) { + material("obj.marble_block", 1) + } + } + + /* Curtains - parlour, dining room, bedroom, and portal nexus; one build dresses the room. */ + row("dbrow.poh_torn_curtains") { + option( + "Torn curtains", + level = 2, + xp = 132.0, + hotspots = + listOf( + "loc.poh_parlour_7", + "loc.poh_dining_room_5", + "loc.poh_bedroom_4", + "loc.poh_telenexus_3", + ), + built = + listOf( + "loc.poh_curtains_1", + "loc.poh_curtains_1", + "loc.poh_curtains_1", + "loc.poh_curtains_1", + ), + groupBuild = true, + nails = 3, + ) { + material("obj.woodplank", 3) + material("obj.cloth", 3) + } + } + row("dbrow.poh_curtains") { + option( + "Curtains", + level = 18, + xp = 225.0, + hotspots = + listOf( + "loc.poh_parlour_7", + "loc.poh_dining_room_5", + "loc.poh_bedroom_4", + "loc.poh_telenexus_3", + ), + built = + listOf( + "loc.poh_curtains_2", + "loc.poh_curtains_2", + "loc.poh_curtains_2", + "loc.poh_curtains_2", + ), + groupBuild = true, + ) { + material("obj.plank_oak", 3) + material("obj.cloth", 3) + } + } + row("dbrow.poh_opulent_curtains") { + option( + "Opulent curtains", + level = 40, + xp = 315.0, + hotspots = + listOf( + "loc.poh_parlour_7", + "loc.poh_dining_room_5", + "loc.poh_bedroom_4", + "loc.poh_telenexus_3", + ), + built = + listOf( + "loc.poh_curtains_3", + "loc.poh_curtains_3", + "loc.poh_curtains_3", + "loc.poh_curtains_3", + ), + groupBuild = true, + ) { + material("obj.plank_teak", 3) + material("obj.cloth", 3) + } + } + + /* Rugs - parlour, bedroom, chapel, and portal nexus; corner/side/middle build as one unit. */ + row("dbrow.poh_brown_rug") { + option( + "Brown rug", + level = 2, + xp = 30.0, + hotspots = + listOf( + "loc.poh_parlour_4_corner", + "loc.poh_parlour_4_side", + "loc.poh_parlour_4_middle", + "loc.poh_bedroom_5_corner", + "loc.poh_bedroom_5_side", + "loc.poh_bedroom_5_middle", + "loc.poh_chapel_5_corner", + "loc.poh_chapel_5_side", + "loc.poh_telenexus_2_corner", + "loc.poh_telenexus_2_side", + "loc.poh_telenexus_2_middle", + ), + built = + listOf( + "loc.poh_rugcorner1", + "loc.poh_rugside1", + "loc.poh_rugmiddle1", + "loc.poh_rugcorner1", + "loc.poh_rugside1", + "loc.poh_rugmiddle1", + "loc.poh_rugcorner1", + "loc.poh_rugside1", + "loc.poh_rugcorner1", + "loc.poh_rugside1", + "loc.poh_rugmiddle1", + ), + groupBuild = true, + ) { + material("obj.cloth", 2) + } + } + row("dbrow.poh_rug") { + option( + "Rug", + level = 13, + xp = 60.0, + hotspots = + listOf( + "loc.poh_parlour_4_corner", + "loc.poh_parlour_4_side", + "loc.poh_parlour_4_middle", + "loc.poh_bedroom_5_corner", + "loc.poh_bedroom_5_side", + "loc.poh_bedroom_5_middle", + "loc.poh_chapel_5_corner", + "loc.poh_chapel_5_side", + "loc.poh_telenexus_2_corner", + "loc.poh_telenexus_2_side", + "loc.poh_telenexus_2_middle", + ), + built = + listOf( + "loc.poh_rugcorner2", + "loc.poh_rugside2", + "loc.poh_rugmiddle2", + "loc.poh_rugcorner2", + "loc.poh_rugside2", + "loc.poh_rugmiddle2", + "loc.poh_rugcorner2", + "loc.poh_rugside2", + "loc.poh_rugcorner2", + "loc.poh_rugside2", + "loc.poh_rugmiddle2", + ), + groupBuild = true, + ) { + material("obj.cloth", 4) + } + } + row("dbrow.poh_opulent_rug") { + option( + "Opulent rug", + level = 65, + xp = 360.0, + hotspots = + listOf( + "loc.poh_parlour_4_corner", + "loc.poh_parlour_4_side", + "loc.poh_parlour_4_middle", + "loc.poh_bedroom_5_corner", + "loc.poh_bedroom_5_side", + "loc.poh_bedroom_5_middle", + "loc.poh_chapel_5_corner", + "loc.poh_chapel_5_side", + "loc.poh_telenexus_2_corner", + "loc.poh_telenexus_2_side", + "loc.poh_telenexus_2_middle", + ), + built = + listOf( + "loc.poh_rugcorner3", + "loc.poh_rugside3", + "loc.poh_rugmiddle3", + "loc.poh_rugcorner3", + "loc.poh_rugside3", + "loc.poh_rugmiddle3", + "loc.poh_rugcorner3", + "loc.poh_rugside3", + "loc.poh_rugcorner3", + "loc.poh_rugside3", + "loc.poh_rugmiddle3", + ), + groupBuild = true, + ) { + material("obj.cloth", 4) + material("obj.gold_leaf", 1) + } + } + + // TODO(unmatched): Deadman rug / Raging echoes rug / Raging echoes curtains - leagues and + // deadman cosmetic override objs are absent from rev 240 gamevals; the base rug/curtain rows + // above and below cover every rug and curtain hotspot. + + /* Kitchen. */ + row("dbrow.poh_firepit") { + option( + "Firepit", + level = 5, + xp = 40.0, + hotspots = listOf("loc.poh_kitchen_1"), + built = listOf("loc.poh_stove_1"), + ) { + material("obj.steel_bar", 1) + material("obj.softclay", 2) + } + } + row("dbrow.poh_firepit_with_hook") { + option( + "Firepit with hook", + level = 11, + xp = 60.0, + hotspots = listOf("loc.poh_kitchen_1"), + built = listOf("loc.poh_stove_2"), + ) { + material("obj.steel_bar", 2) + material("obj.softclay", 2) + } + } + row("dbrow.poh_firepit_with_pot") { + option( + "Firepit with pot", + level = 17, + xp = 80.0, + hotspots = listOf("loc.poh_kitchen_1"), + built = listOf("loc.poh_stove_3"), + ) { + material("obj.steel_bar", 3) + material("obj.softclay", 2) + } + } + row("dbrow.poh_small_oven") { + option( + "Small oven", + level = 24, + xp = 80.0, + hotspots = listOf("loc.poh_kitchen_1"), + built = listOf("loc.poh_stove_4"), + ) { + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_large_oven") { + option( + "Large oven", + level = 29, + xp = 100.0, + hotspots = listOf("loc.poh_kitchen_1"), + built = listOf("loc.poh_stove_5"), + ) { + material("obj.steel_bar", 5) + } + } + row("dbrow.poh_steel_range") { + option( + "Steel range", + level = 34, + xp = 120.0, + hotspots = listOf("loc.poh_kitchen_1"), + built = listOf("loc.poh_stove_6"), + ) { + material("obj.steel_bar", 6) + } + } + row("dbrow.poh_fancy_range") { + option( + "Fancy range", + level = 42, + xp = 160.0, + hotspots = listOf("loc.poh_kitchen_1"), + built = listOf("loc.poh_stove_7"), + ) { + material("obj.steel_bar", 8) + } + } + row("dbrow.poh_wooden_shelves_1") { + option( + "Wooden shelves 1", + level = 6, + xp = 87.0, + hotspots = listOf("loc.poh_kitchen_2", "loc.poh_kitchen_2_crockery"), + built = listOf("loc.poh_kitchen_shelves_1", "loc.poh_kitchen_crockery_1"), + groupBuild = true, + nails = 3, + ) { + material("obj.woodplank", 3) + } + } + row("dbrow.poh_wooden_shelves_2") { + option( + "Wooden shelves 2", + level = 12, + xp = 147.0, + hotspots = listOf("loc.poh_kitchen_2", "loc.poh_kitchen_2_crockery"), + built = listOf("loc.poh_kitchen_shelves_2", "loc.poh_kitchen_crockery_2"), + groupBuild = true, + nails = 3, + ) { + material("obj.woodplank", 3) + material("obj.softclay", 6) + } + } + row("dbrow.poh_wooden_shelves_3") { + option( + "Wooden shelves 3", + level = 23, + xp = 147.0, + hotspots = listOf("loc.poh_kitchen_2", "loc.poh_kitchen_2_crockery"), + built = listOf("loc.poh_kitchen_shelves_3", "loc.poh_kitchen_crockery_3"), + groupBuild = true, + nails = 3, + ) { + material("obj.woodplank", 3) + material("obj.softclay", 6) + } + } + row("dbrow.poh_oak_shelves_1") { + option( + "Oak shelves 1", + level = 34, + xp = 240.0, + hotspots = listOf("loc.poh_kitchen_2", "loc.poh_kitchen_2_crockery"), + built = listOf("loc.poh_kitchen_shelves_4", "loc.poh_kitchen_crockery_4"), + groupBuild = true, + ) { + material("obj.plank_oak", 3) + material("obj.softclay", 6) + } + } + row("dbrow.poh_oak_shelves_2") { + option( + "Oak shelves 2", + level = 45, + xp = 240.0, + hotspots = listOf("loc.poh_kitchen_2", "loc.poh_kitchen_2_crockery"), + built = listOf("loc.poh_kitchen_shelves_5", "loc.poh_kitchen_crockery_5"), + groupBuild = true, + ) { + material("obj.plank_oak", 3) + material("obj.softclay", 6) + } + } + row("dbrow.poh_teak_shelves_1") { + option( + "Teak shelves 1", + level = 56, + xp = 330.0, + hotspots = listOf("loc.poh_kitchen_2", "loc.poh_kitchen_2_crockery"), + built = listOf("loc.poh_kitchen_shelves_6", "loc.poh_kitchen_crockery_6"), + groupBuild = true, + ) { + material("obj.plank_teak", 3) + material("obj.softclay", 6) + } + } + row("dbrow.poh_teak_shelves_2") { + option( + "Teak shelves 2", + level = 67, + xp = 930.0, + hotspots = listOf("loc.poh_kitchen_2", "loc.poh_kitchen_2_crockery"), + built = listOf("loc.poh_kitchen_shelves_7", "loc.poh_kitchen_crockery_7"), + groupBuild = true, + ) { + material("obj.plank_teak", 3) + material("obj.softclay", 6) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_pump_and_drain") { + option( + "Pump and drain", + level = 7, + xp = 100.0, + hotspots = listOf("loc.poh_kitchen_6"), + built = listOf("loc.poh_sink_1"), + ) { + material("obj.steel_bar", 5) + } + } + row("dbrow.poh_pump_and_tub") { + option( + "Pump and tub", + level = 27, + xp = 200.0, + hotspots = listOf("loc.poh_kitchen_6"), + built = listOf("loc.poh_sink_2"), + ) { + material("obj.steel_bar", 10) + } + } + row("dbrow.poh_kitchen_sink") { + option( + "Sink", + level = 47, + xp = 300.0, + hotspots = listOf("loc.poh_kitchen_6"), + built = listOf("loc.poh_sink_3"), + ) { + material("obj.steel_bar", 15) + } + } + row("dbrow.poh_gold_sink") { + option( + "Gold sink", + level = 47, + xp = 11144.0, + hotspots = listOf("loc.poh_kitchen_6"), + built = listOf("loc.poh_sink_4"), + ) { + material("obj.poh_condensed_gold", 10) + material("obj.plank_mahogany", 5) + material("obj.gold_leaf", 5) + } + } + row("dbrow.poh_wooden_larder") { + option( + "Wooden larder", + level = 9, + xp = 228.0, + hotspots = listOf("loc.poh_kitchen_5"), + built = listOf("loc.poh_larder_1"), + nails = 8, + ) { + material("obj.woodplank", 8) + } + } + row("dbrow.poh_oak_larder") { + option( + "Oak larder", + level = 33, + xp = 480.0, + hotspots = listOf("loc.poh_kitchen_5"), + built = listOf("loc.poh_larder_2"), + ) { + material("obj.plank_oak", 8) + } + } + row("dbrow.poh_teak_larder") { + option( + "Teak larder", + level = 43, + xp = 750.0, + hotspots = listOf("loc.poh_kitchen_5"), + built = listOf("loc.poh_larder_3"), + ) { + material("obj.plank_teak", 8) + material("obj.cloth", 2) + } + } + row("dbrow.poh_beer_barrel") { + option( + "Beer barrel", + level = 7, + xp = 87.0, + hotspots = listOf("loc.poh_kitchen_3"), + built = listOf("loc.poh_barrel_1"), + nails = 3, + flatpack = "obj.poh_flatpack_beerbarrel1", + ) { + material("obj.woodplank", 3) + } + } + row("dbrow.poh_cider_barrel") { + option( + "Cider barrel", + level = 12, + xp = 91.0, + hotspots = listOf("loc.poh_kitchen_3"), + built = listOf("loc.poh_barrel_2"), + nails = 3, + flatpack = "obj.poh_flatpack_beerbarrel2", + ) { + material("obj.woodplank", 3) + material("obj.cider", 8) + } + } + row("dbrow.poh_asgarnian_ale_barrel") { + option( + "Asgarnian ale", + level = 18, + xp = 184.0, + hotspots = listOf("loc.poh_kitchen_3"), + built = listOf("loc.poh_barrel_3"), + flatpack = "obj.poh_flatpack_beerbarrel3", + ) { + material("obj.plank_oak", 3) + material("obj.asgarnian_ale", 8) + } + } + row("dbrow.poh_greenmans_ale_barrel") { + option( + "Greenman's ale", + level = 26, + xp = 184.0, + hotspots = listOf("loc.poh_kitchen_3"), + built = listOf("loc.poh_barrel_4"), + flatpack = "obj.poh_flatpack_beerbarrel4", + ) { + material("obj.plank_oak", 3) + material("obj.greenmans_ale", 8) + } + } + row("dbrow.poh_dragon_bitter_barrel") { + option( + "Dragon bitter", + level = 36, + xp = 224.0, + hotspots = listOf("loc.poh_kitchen_3"), + built = listOf("loc.poh_barrel_5"), + flatpack = "obj.poh_flatpack_beerbarrel5", + ) { + material("obj.plank_oak", 3) + material("obj.steel_bar", 2) + material("obj.dragon_bitter", 8) + } + } + row("dbrow.poh_chefs_delight_barrel") { + option( + "Chef's delight", + level = 48, + xp = 224.0, + hotspots = listOf("loc.poh_kitchen_3"), + built = listOf("loc.poh_barrel_6"), + flatpack = "obj.poh_flatpack_beerbarrel6", + ) { + material("obj.plank_oak", 3) + material("obj.steel_bar", 2) + material("obj.chefs_delight", 8) + } + } + row("dbrow.poh_cat_blanket") { + option( + "Cat blanket", + level = 5, + xp = 15.0, + hotspots = listOf("loc.poh_kitchen_4"), + built = listOf("loc.poh_pet_1"), + ) { + material("obj.cloth", 1) + } + } + row("dbrow.poh_cat_basket") { + option( + "Cat basket", + level = 19, + xp = 58.0, + hotspots = listOf("loc.poh_kitchen_4"), + built = listOf("loc.poh_pet_2"), + nails = 2, + ) { + material("obj.woodplank", 2) + } + } + row("dbrow.poh_cushioned_basket") { + option( + "Cushioned basket", + level = 33, + xp = 58.0, + hotspots = listOf("loc.poh_kitchen_4"), + built = listOf("loc.poh_pet_3"), + nails = 2, + ) { + material("obj.woodplank", 2) + material("obj.wool", 2) + } + } + row("dbrow.poh_kitchen_table") { + option( + "Kitchen table", + level = 12, + xp = 87.0, + hotspots = listOf("loc.poh_kitchen_7"), + built = listOf("loc.poh_kitchentable_1"), + nails = 3, + flatpack = "obj.poh_flatpack_kitchentable1", + ) { + material("obj.woodplank", 3) + } + } + row("dbrow.poh_oak_kitchen_table") { + option( + "Oak kitchen table", + level = 32, + xp = 180.0, + hotspots = listOf("loc.poh_kitchen_7"), + built = listOf("loc.poh_kitchentable_2"), + flatpack = "obj.poh_flatpack_kitchentable2", + ) { + material("obj.plank_oak", 3) + } + } + row("dbrow.poh_teak_kitchen_table") { + option( + "Teak kitchen table", + level = 52, + xp = 270.0, + hotspots = listOf("loc.poh_kitchen_7"), + built = listOf("loc.poh_kitchentable_3"), + flatpack = "obj.poh_flatpack_kitchentable3", + ) { + material("obj.plank_teak", 3) + } + } + row("dbrow.poh_spice_rack") { + option( + "Spice rack", + level = 60, + xp = 374.0, + hotspots = listOf("loc.poh_kitchen_8"), + built = listOf("loc.poh_kitchen_rack_1"), + ) { + material("obj.plank_teak", 3) + material("obj.softclay", 6) + material("obj.hundred_dave_spice_brown_4", 1) + material("obj.hundred_dave_spice_orange_4", 1) + material("obj.hundred_dave_spice_red_4", 1) + material("obj.hundred_dave_spice_yellow_4", 1) + } + } + + /* Dining room - wall decorations are shared with the combat and throne room spaces. */ + row("dbrow.poh_wood_dining_table") { + option( + "Wood dining table", + level = 10, + xp = 115.0, + hotspots = listOf("loc.poh_dining_room_1"), + built = listOf("loc.poh_diningtable_1"), + nails = 4, + flatpack = "obj.poh_flatpack_diningtable1", + ) { + material("obj.woodplank", 4) + } + } + row("dbrow.poh_oak_dining_table") { + option( + "Oak dining table", + level = 22, + xp = 240.0, + hotspots = listOf("loc.poh_dining_room_1"), + built = listOf("loc.poh_diningtable_2"), + flatpack = "obj.poh_flatpack_diningtable2", + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_carved_oak_table") { + option( + "Carved oak table", + level = 31, + xp = 360.0, + hotspots = listOf("loc.poh_dining_room_1"), + built = listOf("loc.poh_diningtable_3"), + flatpack = "obj.poh_flatpack_diningtable3", + ) { + material("obj.plank_oak", 6) + } + } + row("dbrow.poh_teak_table") { + option( + "Teak table", + level = 38, + xp = 360.0, + hotspots = listOf("loc.poh_dining_room_1"), + built = listOf("loc.poh_diningtable_4"), + flatpack = "obj.poh_flatpack_diningtable4", + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_carved_teak_table") { + option( + "Carved teak table", + level = 45, + xp = 600.0, + hotspots = listOf("loc.poh_dining_room_1"), + built = listOf("loc.poh_diningtable_5"), + flatpack = "obj.poh_flatpack_diningtable5", + ) { + material("obj.plank_teak", 6) + material("obj.cloth", 4) + } + } + row("dbrow.poh_mahogany_table") { + option( + "Mahogany table", + level = 52, + xp = 840.0, + hotspots = listOf("loc.poh_dining_room_1"), + built = listOf("loc.poh_diningtable_6"), + flatpack = "obj.poh_flatpack_diningtable6", + ) { + material("obj.plank_mahogany", 6) + } + } + row("dbrow.poh_opulent_table") { + option( + "Opulent table", + level = 72, + xp = 3100.0, + hotspots = listOf("loc.poh_dining_room_1"), + built = listOf("loc.poh_diningtable_7"), + flatpack = "obj.poh_flatpack_diningtable7", + ) { + material("obj.plank_mahogany", 6) + material("obj.cloth", 4) + material("obj.gold_leaf", 4) + material("obj.marble_block", 2) + } + } + row("dbrow.poh_wooden_bench") { + option( + "Wooden bench", + level = 10, + xp = 115.0, + hotspots = listOf("loc.poh_dining_room_2"), + built = listOf("loc.poh_diningchairs_1"), + groupBuild = true, + nails = 4, + flatpack = "obj.poh_flatpack_diningchair1", + ) { + material("obj.woodplank", 4) + } + } + row("dbrow.poh_wooden_bench_2") { + option( + "Wooden bench", + level = 10, + xp = 115.0, + hotspots = listOf("loc.poh_dining_room_3"), + built = listOf("loc.poh_diningchairs_1"), + groupBuild = true, + nails = 4, + flatpack = "obj.poh_flatpack_diningchair1", + ) { + material("obj.woodplank", 4) + } + } + row("dbrow.poh_oak_bench") { + option( + "Oak bench", + level = 22, + xp = 240.0, + hotspots = listOf("loc.poh_dining_room_2"), + built = listOf("loc.poh_diningchairs_2"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair2", + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_oak_bench_2") { + option( + "Oak bench", + level = 22, + xp = 240.0, + hotspots = listOf("loc.poh_dining_room_3"), + built = listOf("loc.poh_diningchairs_2"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair2", + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_carved_oak_bench") { + option( + "Carved oak bench", + level = 31, + xp = 240.0, + hotspots = listOf("loc.poh_dining_room_2"), + built = listOf("loc.poh_diningchairs_3"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair3", + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_carved_oak_bench_2") { + option( + "Carved oak bench", + level = 31, + xp = 240.0, + hotspots = listOf("loc.poh_dining_room_3"), + built = listOf("loc.poh_diningchairs_3"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair3", + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_teak_dining_bench") { + option( + "Teak dining bench", + level = 38, + xp = 360.0, + hotspots = listOf("loc.poh_dining_room_2"), + built = listOf("loc.poh_diningchairs_4"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair4", + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_teak_dining_bench_2") { + option( + "Teak dining bench", + level = 38, + xp = 360.0, + hotspots = listOf("loc.poh_dining_room_3"), + built = listOf("loc.poh_diningchairs_4"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair4", + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_carved_teak_bench") { + option( + "Carved teak bench", + level = 44, + xp = 360.0, + hotspots = listOf("loc.poh_dining_room_2"), + built = listOf("loc.poh_diningchairs_5"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair5", + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_carved_teak_bench_2") { + option( + "Carved teak bench", + level = 44, + xp = 360.0, + hotspots = listOf("loc.poh_dining_room_3"), + built = listOf("loc.poh_diningchairs_5"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair5", + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_mahogany_bench") { + option( + "Mahogany bench", + level = 52, + xp = 560.0, + hotspots = listOf("loc.poh_dining_room_2"), + built = listOf("loc.poh_diningchairs_6"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair6", + ) { + material("obj.plank_mahogany", 4) + } + } + row("dbrow.poh_mahogany_bench_2") { + option( + "Mahogany bench", + level = 52, + xp = 560.0, + hotspots = listOf("loc.poh_dining_room_3"), + built = listOf("loc.poh_diningchairs_6"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair6", + ) { + material("obj.plank_mahogany", 4) + } + } + row("dbrow.poh_gilded_bench") { + option( + "Gilded bench", + level = 61, + xp = 1760.0, + hotspots = listOf("loc.poh_dining_room_2"), + built = listOf("loc.poh_diningchairs_7"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair7", + ) { + material("obj.plank_mahogany", 4) + material("obj.gold_leaf", 4) + } + } + row("dbrow.poh_gilded_bench_2") { + option( + "Gilded bench", + level = 61, + xp = 1760.0, + hotspots = listOf("loc.poh_dining_room_3"), + built = listOf("loc.poh_diningchairs_7"), + groupBuild = true, + flatpack = "obj.poh_flatpack_diningchair7", + ) { + material("obj.plank_mahogany", 4) + material("obj.gold_leaf", 4) + } + } + row("dbrow.poh_rope_bellpull") { + option( + "Rope bell-pull", + level = 26, + xp = 64.0, + hotspots = listOf("loc.poh_dining_room_7"), + built = listOf("loc.poh_bellpull_1"), + ) { + material("obj.plank_oak", 1) + material("obj.rope", 1) + } + } + row("dbrow.poh_bellpull") { + option( + "Bell-pull", + level = 37, + xp = 120.0, + hotspots = listOf("loc.poh_dining_room_7"), + built = listOf("loc.poh_bellpull_2"), + ) { + material("obj.plank_teak", 1) + material("obj.cloth", 2) + } + } + row("dbrow.poh_posh_bellpull") { + option( + "Posh bell-pull", + level = 60, + xp = 420.0, + hotspots = listOf("loc.poh_dining_room_7"), + built = listOf("loc.poh_bellpull_3"), + ) { + material("obj.plank_teak", 1) + material("obj.cloth", 2) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_greenman_carving") { + option( + "Greenman carving", + level = 1, + xp = 0.0, + hotspots = + listOf( + "loc.poh_dining_room_6", + "loc.poh_combat_room_5", + "loc.poh_throne_room_3_q", + ), + built = + listOf( + "loc.poh_greenman_wall_decoration", + "loc.poh_greenman_wall_decoration", + "loc.poh_greenman_wall_decoration", + ), + ) { + material("obj.greenman_wall_decoration", 1) + } + } + row("dbrow.poh_oak_wall_decoration") { + option( + "Oak wall decoration", + level = 16, + xp = 120.0, + hotspots = + listOf( + "loc.poh_dining_room_6", + "loc.poh_combat_room_5", + "loc.poh_throne_room_3_q", + ), + built = + listOf( + "loc.poh_wall_deco_1", + "loc.poh_wall_deco_1", + "loc.poh_wall_deco_1", + ), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_teak_wall_decoration") { + option( + "Teak wall decoration", + level = 36, + xp = 180.0, + hotspots = + listOf( + "loc.poh_dining_room_6", + "loc.poh_combat_room_5", + "loc.poh_throne_room_3_q", + ), + built = + listOf( + "loc.poh_wall_deco_2", + "loc.poh_wall_deco_2", + "loc.poh_wall_deco_2", + ), + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_gilded_decoration") { + option( + "Gilded decoration", + level = 56, + xp = 1020.0, + hotspots = + listOf( + "loc.poh_dining_room_6", + "loc.poh_combat_room_5", + "loc.poh_throne_room_3_q", + ), + built = + listOf( + "loc.poh_wall_deco_3", + "loc.poh_wall_deco_3", + "loc.poh_wall_deco_3", + ), + ) { + material("obj.plank_mahogany", 3) + material("obj.gold_leaf", 2) + } + } + + /* Bedroom. */ + row("dbrow.poh_wooden_bed") { + option( + "Wooden bed", + level = 20, + xp = 117.0, + hotspots = listOf("loc.poh_bedroom_1_doublebed"), + built = listOf("loc.poh_bed_1"), + nails = 3, + flatpack = "obj.poh_flatpack_bed1", + ) { + material("obj.woodplank", 3) + material("obj.cloth", 2) + } + } + row("dbrow.poh_oak_bed") { + option( + "Oak bed", + level = 30, + xp = 210.0, + hotspots = listOf("loc.poh_bedroom_1_doublebed"), + built = listOf("loc.poh_bed_2"), + flatpack = "obj.poh_flatpack_bed2", + ) { + material("obj.plank_oak", 3) + material("obj.cloth", 2) + } + } + row("dbrow.poh_large_oak_bed") { + option( + "Large oak bed", + level = 34, + xp = 330.0, + hotspots = listOf("loc.poh_bedroom_1_doublebed"), + built = listOf("loc.poh_bed_3"), + flatpack = "obj.poh_flatpack_bed3", + ) { + material("obj.plank_oak", 5) + material("obj.cloth", 2) + } + } + row("dbrow.poh_teak_bed") { + option( + "Teak bed", + level = 40, + xp = 300.0, + hotspots = listOf("loc.poh_bedroom_1_doublebed"), + built = listOf("loc.poh_bed_4"), + flatpack = "obj.poh_flatpack_bed4", + ) { + material("obj.plank_teak", 3) + material("obj.cloth", 2) + } + } + row("dbrow.poh_large_teak_bed") { + option( + "Large teak bed", + level = 45, + xp = 480.0, + hotspots = listOf("loc.poh_bedroom_1_doublebed"), + built = listOf("loc.poh_bed_5"), + flatpack = "obj.poh_flatpack_bed5", + ) { + material("obj.plank_teak", 5) + material("obj.cloth", 2) + } + } + row("dbrow.poh_four_poster") { + option( + "4-poster", + level = 53, + xp = 450.0, + hotspots = listOf("loc.poh_bedroom_1_doublebed"), + built = listOf("loc.poh_bed_6"), + flatpack = "obj.poh_flatpack_bed6", + ) { + material("obj.plank_mahogany", 3) + material("obj.cloth", 2) + } + } + row("dbrow.poh_gilded_four_poster") { + option( + "Gilded 4-poster", + level = 60, + xp = 1330.0, + hotspots = listOf("loc.poh_bedroom_1_doublebed"), + built = listOf("loc.poh_bed_7"), + flatpack = "obj.poh_flatpack_bed7", + ) { + material("obj.plank_mahogany", 5) + material("obj.cloth", 2) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_shoe_box") { + option( + "Shoe box", + level = 20, + xp = 58.0, + hotspots = listOf("loc.poh_bedroom_2"), + built = listOf("loc.poh_wardrobe_1"), + nails = 2, + flatpack = "obj.poh_flatpack_wardrobe1", + ) { + material("obj.woodplank", 2) + } + } + row("dbrow.poh_oak_drawers") { + option( + "Oak drawers", + level = 27, + xp = 120.0, + hotspots = listOf("loc.poh_bedroom_2"), + built = listOf("loc.poh_wardrobe_2"), + flatpack = "obj.poh_flatpack_wardrobe2", + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_oak_wardrobe") { + option( + "Oak wardrobe", + level = 39, + xp = 180.0, + hotspots = listOf("loc.poh_bedroom_2"), + built = listOf("loc.poh_wardrobe_3"), + flatpack = "obj.poh_flatpack_wardrobe3", + ) { + material("obj.plank_oak", 3) + } + } + row("dbrow.poh_teak_drawers") { + option( + "Teak drawers", + level = 51, + xp = 180.0, + hotspots = listOf("loc.poh_bedroom_2"), + built = listOf("loc.poh_wardrobe_4"), + flatpack = "obj.poh_flatpack_wardrobe4", + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_teak_wardrobe") { + option( + "Teak wardrobe", + level = 63, + xp = 270.0, + hotspots = listOf("loc.poh_bedroom_2"), + built = listOf("loc.poh_wardrobe_5"), + flatpack = "obj.poh_flatpack_wardrobe5", + ) { + material("obj.plank_teak", 3) + } + } + row("dbrow.poh_mahogany_wardrobe") { + option( + "Mahogany wardrobe", + level = 75, + xp = 420.0, + hotspots = listOf("loc.poh_bedroom_2"), + built = listOf("loc.poh_wardrobe_6"), + flatpack = "obj.poh_flatpack_wardrobe6", + ) { + material("obj.plank_mahogany", 3) + } + } + row("dbrow.poh_gilded_wardrobe") { + option( + "Gilded wardrobe", + level = 87, + xp = 720.0, + hotspots = listOf("loc.poh_bedroom_2"), + built = listOf("loc.poh_wardrobe_7"), + flatpack = "obj.poh_flatpack_wardrobe7", + ) { + material("obj.plank_mahogany", 3) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_shaving_stand") { + option( + "Shaving stand", + level = 21, + xp = 30.0, + hotspots = listOf("loc.poh_bedroom_3"), + built = listOf("loc.poh_mirror_1"), + nails = 1, + flatpack = "obj.poh_flatpack_dresser1", + ) { + material("obj.woodplank", 1) + material("obj.molten_glass", 1) + } + } + row("dbrow.poh_oak_shaving_stand") { + option( + "Oak shaving stand", + level = 29, + xp = 61.0, + hotspots = listOf("loc.poh_bedroom_3"), + built = listOf("loc.poh_mirror_2"), + flatpack = "obj.poh_flatpack_dresser2", + ) { + material("obj.plank_oak", 1) + material("obj.molten_glass", 1) + } + } + row("dbrow.poh_oak_dresser") { + option( + "Oak dresser", + level = 37, + xp = 121.0, + hotspots = listOf("loc.poh_bedroom_3"), + built = listOf("loc.poh_mirror_3"), + flatpack = "obj.poh_flatpack_dresser3", + ) { + material("obj.plank_oak", 2) + material("obj.molten_glass", 1) + } + } + row("dbrow.poh_teak_dresser") { + option( + "Teak dresser", + level = 46, + xp = 181.0, + hotspots = listOf("loc.poh_bedroom_3"), + built = listOf("loc.poh_mirror_4"), + flatpack = "obj.poh_flatpack_dresser4", + ) { + material("obj.plank_teak", 2) + material("obj.molten_glass", 1) + } + } + row("dbrow.poh_fancy_teak_dresser") { + option( + "Fancy teak dresser", + level = 56, + xp = 182.0, + hotspots = listOf("loc.poh_bedroom_3"), + built = listOf("loc.poh_mirror_5"), + flatpack = "obj.poh_flatpack_dresser5", + ) { + material("obj.plank_teak", 2) + material("obj.molten_glass", 2) + } + } + row("dbrow.poh_mahogany_dresser") { + option( + "Mahogany dresser", + level = 64, + xp = 281.0, + hotspots = listOf("loc.poh_bedroom_3"), + built = listOf("loc.poh_mirror_6"), + flatpack = "obj.poh_flatpack_dresser6", + ) { + material("obj.plank_mahogany", 2) + material("obj.molten_glass", 1) + } + } + row("dbrow.poh_gilded_dresser") { + option( + "Gilded dresser", + level = 74, + xp = 582.0, + hotspots = listOf("loc.poh_bedroom_3"), + built = listOf("loc.poh_mirror_7"), + flatpack = "obj.poh_flatpack_dresser7", + ) { + material("obj.plank_mahogany", 2) + material("obj.molten_glass", 2) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_oak_clock") { + option( + "Oak clock", + level = 25, + xp = 142.0, + hotspots = listOf("loc.poh_bedroom_7"), + built = listOf("loc.poh_clock_1"), + flatpack = "obj.poh_flatpack_clock1", + ) { + material("obj.plank_oak", 2) + material("obj.poh_clockwork_mechanism", 1) + } + } + row("dbrow.poh_teak_clock") { + option( + "Teak clock", + level = 55, + xp = 202.0, + hotspots = listOf("loc.poh_bedroom_7"), + built = listOf("loc.poh_clock_2"), + flatpack = "obj.poh_flatpack_clock2", + ) { + material("obj.plank_teak", 2) + material("obj.poh_clockwork_mechanism", 1) + } + } + row("dbrow.poh_servants_moneybag") { + option( + "Servant's money bag", + level = 58, + xp = 595.0, + hotspots = listOf("loc.poh_bedroom_7"), + built = listOf("loc.poh_servant_moneybag"), + ) { + material("obj.plank_mahogany", 2) + material("obj.cloth", 1) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_gilded_clock") { + option( + "Gilded clock", + level = 85, + xp = 602.0, + hotspots = listOf("loc.poh_bedroom_7"), + built = listOf("loc.poh_clock_3"), + flatpack = "obj.poh_flatpack_clock3", + ) { + material("obj.plank_mahogany", 2) + material("obj.poh_clockwork_mechanism", 1) + material("obj.gold_leaf", 1) + } + } + + /* Workshop. */ + row("dbrow.poh_wooden_workbench") { + option( + "Wooden workbench", + level = 17, + xp = 143.0, + hotspots = listOf("loc.poh_workshop_1"), + built = listOf("loc.poh_workbench_1"), + nails = 5, + ) { + material("obj.woodplank", 5) + } + } + row("dbrow.poh_oak_workbench") { + option( + "Oak workbench", + level = 32, + xp = 300.0, + hotspots = listOf("loc.poh_workshop_1"), + built = listOf("loc.poh_workbench_2"), + ) { + material("obj.plank_oak", 5) + } + } + row("dbrow.poh_steel_framed_workbench") { + option( + "Steel framed workbench", + level = 46, + xp = 440.0, + hotspots = listOf("loc.poh_workshop_1"), + built = listOf("loc.poh_workbench_3"), + ) { + material("obj.plank_oak", 6) + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_bench_with_vice") { + option( + "Bench with vice", + level = 62, + xp = 140.0, + hotspots = listOf("loc.poh_workbench_3"), + built = listOf("loc.poh_workbench_4"), + ) { + material("obj.plank_oak", 2) + material("obj.steel_bar", 1) + } + } + row("dbrow.poh_bench_with_lathe") { + option( + "Bench with lathe", + level = 77, + xp = 140.0, + hotspots = listOf("loc.poh_workbench_4"), + built = listOf("loc.poh_workbench_5"), + ) { + material("obj.plank_oak", 2) + material("obj.steel_bar", 1) + } + } + row("dbrow.poh_workshop_tool_store_1") { + option( + "Tool store 1", + level = 15, + xp = 120.0, + hotspots = listOf("loc.poh_workshop_3a"), + built = listOf("loc.poh_tools1"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_workshop_tool_store_2") { + option( + "Tool store 2", + level = 25, + xp = 120.0, + hotspots = listOf("loc.poh_workshop_3b"), + built = listOf("loc.poh_tools2"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_workshop_tool_store_3") { + option( + "Tool store 3", + level = 35, + xp = 120.0, + hotspots = listOf("loc.poh_workshop_3c"), + built = listOf("loc.poh_tools3"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_workshop_tool_store_4") { + option( + "Tool store 4", + level = 44, + xp = 120.0, + hotspots = listOf("loc.poh_workshop_3d"), + built = listOf("loc.poh_tools4"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_workshop_tool_store_5") { + option( + "Tool store 5", + level = 55, + xp = 120.0, + hotspots = listOf("loc.poh_workshop_3e"), + built = listOf("loc.poh_tools5"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_workshop_crafting_table_1") { + option( + "Crafting table 1", + level = 16, + xp = 240.0, + hotspots = listOf("loc.poh_workshop_2"), + built = listOf("loc.poh_clockmaking_1"), + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_workshop_crafting_table_2") { + option( + "Crafting table 2", + level = 25, + xp = 1.0, + hotspots = listOf("loc.poh_clockmaking_1"), + built = listOf("loc.poh_clockmaking_2"), + ) { + material("obj.molten_glass", 1) + } + } + row("dbrow.poh_workshop_crafting_table_3") { + option( + "Crafting table 3", + level = 34, + xp = 2.0, + hotspots = listOf("loc.poh_clockmaking_2"), + built = listOf("loc.poh_clockmaking_3"), + ) { + material("obj.molten_glass", 2) + } + } + row("dbrow.poh_workshop_crafting_table_4") { + option( + "Crafting table 4", + level = 42, + xp = 120.0, + hotspots = listOf("loc.poh_clockmaking_3"), + built = listOf("loc.poh_clockmaking_4"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_repair_bench") { + option( + "Repair bench", + level = 15, + xp = 120.0, + hotspots = listOf("loc.poh_workshop_4"), + built = listOf("loc.poh_repair_1"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_whetstone") { + option( + "Whetstone", + level = 35, + xp = 260.0, + hotspots = listOf("loc.poh_workshop_4"), + built = listOf("loc.poh_repair_2"), + ) { + material("obj.plank_oak", 4) + material("obj.limestonebrick", 1) + } + } + row("dbrow.poh_armour_stand") { + option( + "Armour stand", + level = 55, + xp = 500.0, + hotspots = listOf("loc.poh_workshop_4"), + built = listOf("loc.poh_repair_3"), + ) { + material("obj.plank_oak", 8) + material("obj.limestonebrick", 1) + } + } + row("dbrow.poh_pluming_stand") { + option( + "Pluming stand", + level = 16, + xp = 120.0, + hotspots = listOf("loc.poh_workshop_5"), + built = listOf("loc.poh_repair_4"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_shield_easel") { + option( + "Shield easel", + level = 41, + xp = 240.0, + hotspots = listOf("loc.poh_workshop_5"), + built = listOf("loc.poh_repair_5"), + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_banner_easel") { + option( + "Banner easel", + level = 66, + xp = 510.0, + hotspots = listOf("loc.poh_workshop_5"), + built = listOf("loc.poh_repair_6"), + ) { + material("obj.plank_oak", 8) + material("obj.cloth", 2) + } + } + + /* Study. */ + // TODO(unmatched): S.t.a.s.h chart - the S.t.a.s.h blueprint obj is not in rev 240 gamevals. + row("dbrow.poh_oak_lectern") { + option( + "Oak lectern", + level = 40, + xp = 60.0, + hotspots = listOf("loc.poh_study_1"), + built = listOf("loc.poh_lectern_1"), + flatpack = "obj.poh_flatpack_lecturn1", + ) { + material("obj.plank_oak", 1) + } + } + row("dbrow.poh_eagle_lectern") { + option( + "Eagle lectern", + level = 47, + xp = 120.0, + hotspots = listOf("loc.poh_study_1"), + built = listOf("loc.poh_lectern_2"), + flatpack = "obj.poh_flatpack_lecturn2", + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_demon_lectern") { + option( + "Demon lectern", + level = 47, + xp = 120.0, + hotspots = listOf("loc.poh_study_1"), + built = listOf("loc.poh_lectern_3"), + flatpack = "obj.poh_flatpack_lecturn3", + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_teak_eagle_lectern") { + option( + "Teak eagle lectern", + level = 57, + xp = 180.0, + hotspots = listOf("loc.poh_study_1"), + built = listOf("loc.poh_lectern_4"), + flatpack = "obj.poh_flatpack_lecturn4", + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_teak_demon_lectern") { + option( + "Teak demon lectern", + level = 57, + xp = 180.0, + hotspots = listOf("loc.poh_study_1"), + built = listOf("loc.poh_lectern_5"), + flatpack = "obj.poh_flatpack_lecturn5", + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_mahogany_eagle_lectern") { + option( + "Mahogany eagle lectern", + level = 67, + xp = 580.0, + hotspots = listOf("loc.poh_study_1"), + built = listOf("loc.poh_lectern_6"), + flatpack = "obj.poh_flatpack_lecturn6", + ) { + material("obj.plank_mahogany", 2) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_mahogany_demon_lectern") { + option( + "Mahogany demon lectern", + level = 67, + xp = 580.0, + hotspots = listOf("loc.poh_study_1"), + built = listOf("loc.poh_lectern_7"), + flatpack = "obj.poh_flatpack_lecturn7", + ) { + material("obj.plank_mahogany", 2) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_marble_lectern") { + option( + "Marble lectern", + level = 77, + xp = 1800.0, + hotspots = listOf("loc.poh_study_1"), + built = listOf("loc.poh_lectern_8"), + ) { + material("obj.marble_block", 1) + material("obj.poh_magic_crystal", 1) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_globe") { + option( + "Globe", + level = 41, + xp = 180.0, + hotspots = listOf("loc.poh_study_2"), + built = listOf("loc.poh_globe_1"), + flatpack = "obj.poh_flatpack_globe1", + ) { + material("obj.plank_oak", 3) + } + } + row("dbrow.poh_ornamental_globe") { + option( + "Ornamental globe", + level = 50, + xp = 270.0, + hotspots = listOf("loc.poh_study_2"), + built = listOf("loc.poh_globe_2"), + flatpack = "obj.poh_flatpack_globe2", + ) { + material("obj.plank_teak", 3) + } + } + row("dbrow.poh_lunar_globe") { + option( + "Lunar globe", + level = 59, + xp = 570.0, + hotspots = listOf("loc.poh_study_2"), + built = listOf("loc.poh_globe_3"), + flatpack = "obj.poh_flatpack_globe3", + ) { + material("obj.plank_teak", 3) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_celestial_globe") { + option( + "Celestial globe", + level = 68, + xp = 570.0, + hotspots = listOf("loc.poh_study_2"), + built = listOf("loc.poh_globe_4"), + flatpack = "obj.poh_flatpack_globe4", + ) { + material("obj.plank_teak", 3) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_armillary_sphere") { + option( + "Armillary sphere", + level = 77, + xp = 960.0, + hotspots = listOf("loc.poh_study_2"), + built = listOf("loc.poh_globe_5"), + flatpack = "obj.poh_flatpack_globe5", + ) { + material("obj.plank_mahogany", 2) + material("obj.gold_leaf", 2) + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_small_orrery") { + option( + "Small orrery", + level = 86, + xp = 1320.0, + hotspots = listOf("loc.poh_study_2"), + built = listOf("loc.poh_globe_6"), + flatpack = "obj.poh_flatpack_globe6", + ) { + material("obj.plank_mahogany", 3) + material("obj.gold_leaf", 3) + } + } + row("dbrow.poh_large_orrery") { + option( + "Large orrery", + level = 95, + xp = 1420.0, + hotspots = listOf("loc.poh_study_2"), + built = listOf("loc.poh_globe_7"), + flatpack = "obj.poh_flatpack_globe7", + ) { + material("obj.plank_mahogany", 3) + material("obj.gold_leaf", 5) + } + } + row("dbrow.poh_crystal_ball") { + option( + "Crystal ball", + level = 42, + xp = 280.0, + hotspots = listOf("loc.poh_study_4"), + built = listOf("loc.poh_crystalball_1"), + flatpack = "obj.poh_flatpack_crystalball1", + ) { + material("obj.plank_teak", 3) + material("obj.stafforb", 1) + } + } + row("dbrow.poh_elemental_sphere") { + option( + "Elemental sphere", + level = 54, + xp = 580.0, + hotspots = listOf("loc.poh_study_4"), + built = listOf("loc.poh_crystalball_2"), + flatpack = "obj.poh_flatpack_crystalball2", + ) { + material("obj.plank_teak", 3) + material("obj.stafforb", 1) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_crystal_of_power") { + option( + "Crystal of power", + level = 66, + xp = 890.0, + hotspots = listOf("loc.poh_study_4"), + built = listOf("loc.poh_crystalball_3"), + flatpack = "obj.poh_flatpack_crystalball3", + ) { + material("obj.plank_mahogany", 2) + material("obj.stafforb", 1) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_oak_telescope") { + option( + "Oak telescope", + level = 44, + xp = 121.0, + hotspots = listOf("loc.poh_study_6"), + built = listOf("loc.poh_telescope_1"), + flatpack = "obj.poh_flatpack_telescope1", + ) { + material("obj.plank_oak", 2) + material("obj.molten_glass", 1) + } + } + row("dbrow.poh_teak_telescope") { + option( + "Teak telescope", + level = 64, + xp = 181.0, + hotspots = listOf("loc.poh_study_6"), + built = listOf("loc.poh_telescope_2"), + flatpack = "obj.poh_flatpack_telescope2", + ) { + material("obj.plank_teak", 2) + material("obj.molten_glass", 1) + } + } + row("dbrow.poh_mahogany_telescope") { + option( + "Mahogany telescope", + level = 84, + xp = 580.0, + hotspots = listOf("loc.poh_study_6"), + built = listOf("loc.poh_telescope_3"), + flatpack = "obj.poh_flatpack_telescope3", + ) { + material("obj.plank_mahogany", 2) + material("obj.molten_glass", 1) + } + } + row("dbrow.poh_alchemical_chart") { + option( + "Alchemical chart", + level = 43, + xp = 30.0, + hotspots = listOf("loc.poh_study_5"), + built = listOf("loc.poh_wallchart_1"), + ) { + material("obj.cloth", 2) + } + } + row("dbrow.poh_astronomical_chart") { + option( + "Astronomical chart", + level = 63, + xp = 45.0, + hotspots = listOf("loc.poh_study_5"), + built = listOf("loc.poh_wallchart_2"), + ) { + material("obj.cloth", 3) + } + } + row("dbrow.poh_infernal_chart") { + option( + "Infernal chart", + level = 83, + xp = 60.0, + hotspots = listOf("loc.poh_study_5"), + built = listOf("loc.poh_wallchart_3"), + ) { + material("obj.cloth", 4) + } + } + + /* Chapel - altar/statue/window god variants follow the built icon at runtime. */ + // TODO(unmatched): Gnome child icon / Grid Master Icon - unlock objs absent from rev 240. + row("dbrow.poh_oak_altar") { + option( + "Oak altar", + level = 45, + xp = 240.0, + hotspots = listOf("loc.poh_chapel_2"), + built = listOf("loc.poh_altar_saradomin_1"), + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_teak_altar") { + option( + "Teak altar", + level = 50, + xp = 360.0, + hotspots = listOf("loc.poh_chapel_2"), + built = listOf("loc.poh_altar_saradomin_2"), + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_cloth_altar") { + option( + "Cloth altar", + level = 56, + xp = 390.0, + hotspots = listOf("loc.poh_chapel_2"), + built = listOf("loc.poh_altar_saradomin_3"), + ) { + material("obj.plank_teak", 4) + material("obj.cloth", 2) + } + } + row("dbrow.poh_mahogany_altar") { + option( + "Mahogany altar", + level = 60, + xp = 590.0, + hotspots = listOf("loc.poh_chapel_2"), + built = listOf("loc.poh_altar_saradomin_4"), + ) { + material("obj.plank_mahogany", 4) + material("obj.cloth", 2) + } + } + row("dbrow.poh_limestone_altar") { + option( + "Limestone altar", + level = 64, + xp = 910.0, + hotspots = listOf("loc.poh_chapel_2"), + built = listOf("loc.poh_altar_saradomin_5"), + ) { + material("obj.plank_mahogany", 6) + material("obj.cloth", 2) + material("obj.limestonebrick", 2) + } + } + row("dbrow.poh_marble_altar") { + option( + "Marble altar", + level = 70, + xp = 1030.0, + hotspots = listOf("loc.poh_chapel_2"), + built = listOf("loc.poh_altar_saradomin_6"), + ) { + material("obj.marble_block", 2) + material("obj.cloth", 2) + } + } + row("dbrow.poh_gilded_altar") { + option( + "Gilded altar", + level = 75, + xp = 2230.0, + hotspots = listOf("loc.poh_chapel_2"), + built = listOf("loc.poh_altar_saradomin_7"), + ) { + material("obj.marble_block", 2) + material("obj.cloth", 2) + material("obj.gold_leaf", 4) + } + } + row("dbrow.poh_steel_torches") { + option( + "Steel torches", + level = 45, + xp = 40.0, + hotspots = listOf("loc.poh_chapel_3"), + built = listOf("loc.poh_torch_1"), + ) { + material("obj.steel_bar", 2) + } + } + row("dbrow.poh_wooden_torches") { + option( + "Wooden torches", + level = 49, + xp = 58.0, + hotspots = listOf("loc.poh_chapel_3"), + built = listOf("loc.poh_torch_2"), + nails = 2, + ) { + material("obj.woodplank", 2) + } + } + row("dbrow.poh_steel_candlesticks") { + option( + "Steel candlesticks", + level = 53, + xp = 124.0, + hotspots = listOf("loc.poh_chapel_3"), + built = listOf("loc.poh_torch_3"), + ) { + material("obj.steel_bar", 6) + material("obj.unlit_candle", 6) + } + } + row("dbrow.poh_gold_candlesticks") { + option( + "Gold candlesticks", + level = 57, + xp = 46.0, + hotspots = listOf("loc.poh_chapel_3"), + built = listOf("loc.poh_torch_4"), + ) { + material("obj.gold_bar", 6) + material("obj.unlit_candle", 6) + } + } + row("dbrow.poh_oak_incense_burners") { + option( + "Oak incense burners", + level = 61, + xp = 280.0, + hotspots = listOf("loc.poh_chapel_3"), + built = listOf("loc.poh_torch_5"), + ) { + material("obj.plank_oak", 4) + material("obj.steel_bar", 2) + } + } + row("dbrow.poh_mahogany_incense_burners") { + option( + "Mahogany incense burners", + level = 65, + xp = 600.0, + hotspots = listOf("loc.poh_chapel_3"), + built = listOf("loc.poh_torch_6"), + ) { + material("obj.plank_mahogany", 4) + material("obj.steel_bar", 2) + } + } + row("dbrow.poh_marble_incense_burners") { + option( + "Marble incense burners", + level = 69, + xp = 1600.0, + hotspots = listOf("loc.poh_chapel_3"), + built = listOf("loc.poh_torch_7"), + ) { + material("obj.marble_block", 2) + material("obj.steel_bar", 2) + } + } + row("dbrow.poh_saradomin_symbol") { + option( + "Symbol of Saradomin", + level = 48, + xp = 120.0, + hotspots = listOf("loc.poh_chapel_1"), + built = listOf("loc.poh_icon_1"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_zamorak_symbol") { + option( + "Symbol of Zamorak", + level = 48, + xp = 120.0, + hotspots = listOf("loc.poh_chapel_1"), + built = listOf("loc.poh_icon_2"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_guthix_symbol") { + option( + "Symbol of Guthix", + level = 48, + xp = 120.0, + hotspots = listOf("loc.poh_chapel_1"), + built = listOf("loc.poh_icon_3"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_saradomin_icon") { + option( + "Icon of Saradomin", + level = 59, + xp = 960.0, + hotspots = listOf("loc.poh_chapel_1"), + built = listOf("loc.poh_icon_4"), + ) { + material("obj.plank_teak", 4) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_zamorak_icon") { + option( + "Icon of Zamorak", + level = 59, + xp = 960.0, + hotspots = listOf("loc.poh_chapel_1"), + built = listOf("loc.poh_icon_5"), + ) { + material("obj.plank_teak", 4) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_guthix_icon") { + option( + "Icon of Guthix", + level = 59, + xp = 960.0, + hotspots = listOf("loc.poh_chapel_1"), + built = listOf("loc.poh_icon_6"), + ) { + material("obj.plank_teak", 4) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_icon_of_bob") { + option( + "Icon of Bob", + level = 71, + xp = 1160.0, + hotspots = listOf("loc.poh_chapel_1"), + built = listOf("loc.poh_icon_7"), + ) { + material("obj.plank_mahogany", 4) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_chapel_windchimes") { + option( + "Windchimes", + level = 49, + xp = 323.0, + hotspots = listOf("loc.poh_chapel_7"), + built = listOf("loc.poh_musical_thing_1"), + nails = 4, + ) { + material("obj.plank_oak", 4) + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_chapel_bells") { + option( + "Bells", + level = 58, + xp = 480.0, + hotspots = listOf("loc.poh_chapel_7"), + built = listOf("loc.poh_musical_thing_2"), + ) { + material("obj.plank_teak", 4) + material("obj.steel_bar", 6) + } + } + row("dbrow.poh_chapel_organ") { + option( + "Organ", + level = 69, + xp = 680.0, + hotspots = listOf("loc.poh_chapel_7"), + built = listOf("loc.poh_musical_thing_3"), + ) { + material("obj.plank_mahogany", 4) + material("obj.steel_bar", 6) + } + } + row("dbrow.poh_small_statue") { + option( + "Small statue", + level = 49, + xp = 40.0, + hotspots = listOf("loc.poh_chapel_6"), + built = listOf("loc.poh_statue_monk"), + ) { + material("obj.limestonebrick", 2) + } + } + row("dbrow.poh_medium_statue") { + option( + "Medium statue", + level = 69, + xp = 500.0, + hotspots = listOf("loc.poh_chapel_6"), + built = listOf("loc.poh_statue_angel"), + ) { + material("obj.marble_block", 1) + } + } + row("dbrow.poh_large_statue") { + option( + "Large statue", + level = 89, + xp = 1500.0, + hotspots = listOf("loc.poh_chapel_6"), + built = listOf("loc.poh_statue_saradomin"), + ) { + material("obj.marble_block", 3) + } + } + row("dbrow.poh_shuttered_window") { + option( + "Shuttered window", + level = 49, + xp = 228.0, + hotspots = listOf("loc.poh_chapelwindow_hotspot_rimmington"), + built = listOf("loc.poh_rimmington_window_shutters"), + groupBuild = true, + nails = 8, + ) { + material("obj.woodplank", 8) + } + } + row("dbrow.poh_decorative_window") { + option( + "Decorative window", + level = 69, + xp = 4.0, + hotspots = listOf("loc.poh_chapelwindow_hotspot_rimmington"), + built = listOf("loc.poh_rimmington_window_saradomin"), + groupBuild = true, + ) { + material("obj.molten_glass", 8) + } + } + row("dbrow.poh_stained_glass") { + option( + "Stained glass", + level = 89, + xp = 5.0, + hotspots = listOf("loc.poh_chapelwindow_hotspot_rimmington"), + built = listOf("loc.poh_rimmington_window_saradomin2"), + groupBuild = true, + ) { + material("obj.molten_glass", 16) + } + } + + /* Throne room. */ + row("dbrow.poh_oak_throne") { + option( + "Oak throne", + level = 60, + xp = 800.0, + hotspots = listOf("loc.poh_throne_room_1"), + built = listOf("loc.poh_throne_1"), + ) { + material("obj.plank_oak", 5) + material("obj.marble_block", 1) + } + } + row("dbrow.poh_teak_throne") { + option( + "Teak throne", + level = 67, + xp = 1450.0, + hotspots = listOf("loc.poh_throne_room_1"), + built = listOf("loc.poh_throne_2"), + ) { + material("obj.plank_teak", 5) + material("obj.marble_block", 2) + } + } + row("dbrow.poh_mahogany_throne") { + option( + "Mahogany throne", + level = 74, + xp = 2200.0, + hotspots = listOf("loc.poh_throne_room_1"), + built = listOf("loc.poh_throne_3"), + ) { + material("obj.plank_mahogany", 5) + material("obj.marble_block", 3) + } + } + row("dbrow.poh_gilded_throne") { + option( + "Gilded throne", + level = 81, + xp = 2600.0, + hotspots = listOf("loc.poh_throne_room_1"), + built = listOf("loc.poh_throne_4"), + ) { + material("obj.plank_mahogany", 5) + material("obj.marble_block", 2) + material("obj.gold_leaf", 3) + } + } + row("dbrow.poh_skeleton_throne") { + option( + "Skeleton throne", + level = 88, + xp = 7003.0, + hotspots = listOf("loc.poh_throne_room_1"), + built = listOf("loc.poh_throne_5"), + ) { + material("obj.poh_magic_crystal", 5) + material("obj.marble_block", 4) + material("obj.bones", 5) + material("obj.skull", 2) + } + } + row("dbrow.poh_crystal_throne") { + option( + "Crystal throne", + level = 95, + xp = 15000.0, + hotspots = listOf("loc.poh_throne_room_1"), + built = listOf("loc.poh_throne_6"), + ) { + material("obj.poh_magic_crystal", 15) + } + } + row("dbrow.poh_demonic_throne") { + option( + "Demonic throne", + level = 99, + xp = 25000.0, + hotspots = listOf("loc.poh_throne_room_1"), + built = listOf("loc.poh_throne_7"), + ) { + material("obj.poh_magic_crystal", 25) + } + } + row("dbrow.poh_oak_lever") { + option( + "Oak lever", + level = 68, + xp = 300.0, + hotspots = listOf("loc.poh_throne_room_4"), + built = listOf("loc.poh_lever_oak_4"), + ) { + material("obj.plank_oak", 5) + } + } + row("dbrow.poh_teak_lever") { + option( + "Teak lever", + level = 78, + xp = 450.0, + hotspots = listOf("loc.poh_throne_room_4"), + built = listOf("loc.poh_lever_teak_4"), + ) { + material("obj.plank_teak", 5) + } + } + row("dbrow.poh_mahogany_lever") { + option( + "Mahogany lever", + level = 88, + xp = 700.0, + hotspots = listOf("loc.poh_throne_room_4"), + built = listOf("loc.poh_lever_mag_4"), + ) { + material("obj.plank_mahogany", 5) + } + } + row("dbrow.poh_oak_trapdoor") { + option( + "Oak trapdoor", + level = 68, + xp = 300.0, + hotspots = listOf("loc.poh_throne_room_7"), + built = listOf("loc.poh_trapdoor_oak_7"), + ) { + material("obj.plank_oak", 5) + } + } + row("dbrow.poh_teak_trapdoor") { + option( + "Teak trapdoor", + level = 78, + xp = 450.0, + hotspots = listOf("loc.poh_throne_room_7"), + built = listOf("loc.poh_trapdoor_teak_7"), + ) { + material("obj.plank_teak", 5) + } + } + row("dbrow.poh_mahogany_trapdoor") { + option( + "Mahogany trapdoor", + level = 88, + xp = 700.0, + hotspots = listOf("loc.poh_throne_room_7"), + built = listOf("loc.poh_trapdoor_mag_7"), + ) { + material("obj.plank_mahogany", 5) + } + } + row("dbrow.poh_floor_decoration") { + option( + "Floor decoration", + level = 61, + xp = 700.0, + hotspots = listOf("loc.poh_throne_room_3_rimmington"), + built = listOf("loc.poh_floordecor_rimmington"), + groupBuild = true, + ) { + material("obj.plank_mahogany", 5) + } + } + row("dbrow.poh_throne_steel_cage") { + option( + "Steel cage", + level = 68, + xp = 1100.0, + hotspots = listOf("loc.poh_throne_room_3_rimmington"), + built = listOf("loc.poh_cage_throneroom"), + groupBuild = true, + ) { + material("obj.plank_mahogany", 5) + material("obj.steel_bar", 20) + } + } + row("dbrow.poh_trapdoor_floor") { + option( + "Trapdoor", + level = 74, + xp = 770.0, + hotspots = listOf("loc.poh_throne_room_3_rimmington"), + built = listOf("loc.poh_floordecor_rimmington"), + groupBuild = true, + ) { + material("obj.plank_mahogany", 5) + material("obj.poh_clockwork_mechanism", 10) + } + } + row("dbrow.poh_lesser_magic_cage") { + option( + "Lesser magic cage", + level = 82, + xp = 2700.0, + hotspots = listOf("loc.poh_throne_room_3_rimmington"), + built = listOf("loc.poh_magic_cage_lesser"), + groupBuild = true, + ) { + material("obj.plank_mahogany", 5) + material("obj.poh_magic_crystal", 2) + } + } + row("dbrow.poh_greater_magic_cage") { + option( + "Greater magic cage", + level = 89, + xp = 4700.0, + hotspots = listOf("loc.poh_throne_room_3_rimmington"), + built = listOf("loc.poh_magic_cage_greater"), + groupBuild = true, + ) { + material("obj.plank_mahogany", 5) + material("obj.poh_magic_crystal", 4) + } + } + row("dbrow.poh_throne_carved_teak_bench") { + option( + "Carved teak bench", + level = 44, + xp = 360.0, + hotspots = listOf("loc.poh_throne_room_5"), + built = listOf("loc.poh_throneroom_bench_1"), + groupBuild = true, + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_throne_carved_teak_bench_2") { + option( + "Carved teak bench", + level = 44, + xp = 360.0, + hotspots = listOf("loc.poh_throne_room_6"), + built = listOf("loc.poh_throneroom_bench_1"), + groupBuild = true, + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_throne_mahogany_bench") { + option( + "Mahogany bench", + level = 52, + xp = 560.0, + hotspots = listOf("loc.poh_throne_room_5"), + built = listOf("loc.poh_throneroom_bench_2"), + groupBuild = true, + ) { + material("obj.plank_mahogany", 4) + } + } + row("dbrow.poh_throne_mahogany_bench_2") { + option( + "Mahogany bench", + level = 52, + xp = 560.0, + hotspots = listOf("loc.poh_throne_room_6"), + built = listOf("loc.poh_throneroom_bench_2"), + groupBuild = true, + ) { + material("obj.plank_mahogany", 4) + } + } + row("dbrow.poh_throne_gilded_bench") { + option( + "Gilded bench", + level = 61, + xp = 1760.0, + hotspots = listOf("loc.poh_throne_room_5"), + built = listOf("loc.poh_throneroom_bench_3"), + groupBuild = true, + ) { + material("obj.plank_mahogany", 4) + material("obj.gold_leaf", 4) + } + } + row("dbrow.poh_throne_gilded_bench_2") { + option( + "Gilded bench", + level = 61, + xp = 1760.0, + hotspots = listOf("loc.poh_throne_room_6"), + built = listOf("loc.poh_throneroom_bench_3"), + groupBuild = true, + ) { + material("obj.plank_mahogany", 4) + material("obj.gold_leaf", 4) + } + } + row("dbrow.poh_round_shield") { + option( + "Round shield", + level = 66, + xp = 120.0, + hotspots = listOf("loc.poh_throne_room_3_q", "loc.poh_dungeon_treasure_5"), + built = listOf("loc.poh_round_shield_arrav", "loc.poh_round_shield_arrav"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_square_shield") { + option( + "Square shield", + level = 76, + xp = 360.0, + hotspots = listOf("loc.poh_throne_room_3_q", "loc.poh_dungeon_treasure_5"), + built = listOf("loc.poh_square_shield_arrav", "loc.poh_square_shield_arrav"), + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_kite_shield") { + option( + "Kite shield", + level = 86, + xp = 420.0, + hotspots = listOf("loc.poh_throne_room_3_q", "loc.poh_dungeon_treasure_5"), + built = listOf("loc.poh_kite_shield_arrav", "loc.poh_kite_shield_arrav"), + ) { + material("obj.plank_mahogany", 3) + } + } + + /* Games room. */ + row("dbrow.poh_lesser_magical_balance") { + option( + "Lesser magical balance", + level = 37, + xp = 176.0, + hotspots = listOf("loc.poh_games_room_6"), + built = listOf("loc.poh_elemental_orb_1"), + ) { + material("obj.airrune", 500) + material("obj.earthrune", 500) + material("obj.firerune", 500) + material("obj.waterrune", 500) + } + } + row("dbrow.poh_medium_magical_balance") { + option( + "Medium balance", + level = 57, + xp = 252.0, + hotspots = listOf("loc.poh_games_room_6"), + built = listOf("loc.poh_elemental_orb_2"), + ) { + material("obj.airrune", 1000) + material("obj.earthrune", 1000) + material("obj.firerune", 1000) + material("obj.waterrune", 1000) + } + } + row("dbrow.poh_greater_magical_balance") { + option( + "Greater magical balance", + level = 77, + xp = 356.0, + hotspots = listOf("loc.poh_games_room_6"), + built = listOf("loc.poh_elemental_orb_3"), + ) { + material("obj.airrune", 2000) + material("obj.earthrune", 2000) + material("obj.firerune", 2000) + material("obj.waterrune", 2000) + } + } + row("dbrow.poh_jester") { + option( + "Jester", + level = 39, + xp = 360.0, + hotspots = listOf("loc.poh_games_room_2"), + built = listOf("loc.poh_mime_jester"), + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_treasure_hunt") { + option( + "Treasure hunt", + level = 49, + xp = 800.0, + hotspots = listOf("loc.poh_games_room_2"), + built = listOf("loc.poh_fairy_house"), + ) { + material("obj.plank_teak", 8) + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_hangman") { + option( + "Hangman", + level = 59, + xp = 1200.0, + hotspots = listOf("loc.poh_games_room_2"), + built = listOf("loc.poh_hangman_chest"), + ) { + material("obj.plank_teak", 12) + material("obj.steel_bar", 6) + } + } + row("dbrow.poh_oak_prize_chest") { + option( + "Oak prize chest", + level = 34, + xp = 240.0, + hotspots = listOf("loc.poh_games_room_4"), + built = listOf("loc.poh_prize_chest_oak_closed"), + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_teak_prize_chest") { + option( + "Teak prize chest", + level = 44, + xp = 660.0, + hotspots = listOf("loc.poh_games_room_4"), + built = listOf("loc.poh_prize_chest_teak_closed"), + ) { + material("obj.plank_teak", 4) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_mahogany_prize_chest") { + option( + "Mahogany prize chest", + level = 54, + xp = 860.0, + hotspots = listOf("loc.poh_games_room_4"), + built = listOf("loc.poh_prize_chest_mag_closed"), + ) { + material("obj.plank_mahogany", 4) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_hoop_and_stick") { + option( + "Hoop and stick", + level = 30, + xp = 120.0, + hotspots = listOf("loc.poh_games_room_7"), + built = listOf("loc.poh_stick+hoop1"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_dartboard") { + option( + "Dartboard", + level = 54, + xp = 290.0, + hotspots = listOf("loc.poh_games_room_7"), + built = listOf("loc.poh_dartboard1"), + ) { + material("obj.plank_teak", 3) + material("obj.steel_bar", 1) + } + } + row("dbrow.poh_archery_target") { + option( + "Archery target", + level = 81, + xp = 600.0, + hotspots = listOf("loc.poh_games_room_7"), + built = listOf("loc.poh_archery_target1"), + ) { + material("obj.plank_teak", 6) + material("obj.steel_bar", 3) + } + } + row("dbrow.poh_clay_attack_stone") { + option( + "Clay attack stone", + level = 39, + xp = 100.0, + hotspots = listOf("loc.poh_games_room_5"), + built = listOf("loc.poh_tbt_clay_new"), + ) { + material("obj.softclay", 10) + } + } + row("dbrow.poh_limestone_attack_stone") { + option( + "Limestone attack stone", + level = 59, + xp = 200.0, + hotspots = listOf("loc.poh_games_room_5"), + built = listOf("loc.poh_tbt_limestone_new"), + ) { + material("obj.limestonebrick", 10) + } + } + row("dbrow.poh_marble_attack_stone") { + option( + "Marble attack stone", + level = 79, + xp = 2000.0, + hotspots = listOf("loc.poh_games_room_5"), + built = listOf("loc.poh_tbt_marble_new"), + ) { + material("obj.marble_block", 4) + } + } + + /* Combat room - ring pieces map floor mats + combat-family walls per option. */ + // TODO(unmatched): Balance beam - poh_balancebeam_endl/middle/endr cannot be index-aligned to + // the single poh_gr_1_wall_agility hotspot loc. + row("dbrow.poh_boxing_ring") { + option( + "Boxing ring", + level = 32, + xp = 420.0, + hotspots = + listOf( + "loc.poh_gr_1_floor_middle", + "loc.poh_gr_1_floor_side", + "loc.poh_gr_1_floor_n", + "loc.poh_gr_1_floor_s", + "loc.poh_gr_1_floor_ne", + "loc.poh_gr_1_floor_nw", + "loc.poh_gr_1_floor_se", + "loc.poh_gr_1_floor_sw", + "loc.poh_gr_1_wall_combat", + "loc.poh_gr_1_wall_combat_agility", + "loc.poh_gr_1_wall_ranging_combat", + "loc.poh_gr_1_wall_everything", + "loc.poh_gr_1_wall_cobat_corner", + "loc.poh_gr_1_wall_combat_agility_corner", + "loc.poh_gr_1_wall_redcorner", + "loc.poh_gr_1_wall_bluecorner", + ), + built = + listOf( + "loc.poh_boxing_ring_mat_middle", + "loc.poh_boxing_ring_mat_side", + "loc.poh_boxing_ring_mat_side", + "loc.poh_boxing_ring_mat_side", + "loc.poh_boxing_ring_mat_corner", + "loc.poh_boxing_ring_mat_corner", + "loc.poh_boxing_ring_mat_corner", + "loc.poh_boxing_ring_mat_corner", + "loc.poh_boxing_ringwall_white", + "loc.poh_boxing_ringwall_white", + "loc.poh_boxing_ringwall_white", + "loc.poh_boxing_ringwall_white", + "loc.poh_boxing_ringwall_corner", + "loc.poh_boxing_ringwall_corner", + "loc.poh_boxing_ringwall_red", + "loc.poh_boxing_ringwall_blue", + ), + groupBuild = true, + ) { + material("obj.plank_oak", 6) + material("obj.cloth", 4) + } + } + row("dbrow.poh_fencing_ring") { + option( + "Fencing ring", + level = 41, + xp = 570.0, + hotspots = + listOf( + "loc.poh_gr_1_floor_middle", + "loc.poh_gr_1_floor_side", + "loc.poh_gr_1_floor_n", + "loc.poh_gr_1_floor_s", + "loc.poh_gr_1_floor_ne", + "loc.poh_gr_1_floor_nw", + "loc.poh_gr_1_floor_se", + "loc.poh_gr_1_floor_sw", + "loc.poh_gr_1_wall_combat", + "loc.poh_gr_1_wall_combat_agility", + "loc.poh_gr_1_wall_ranging_combat", + "loc.poh_gr_1_wall_everything", + "loc.poh_gr_1_wall_cobat_corner", + "loc.poh_gr_1_wall_combat_agility_corner", + "loc.poh_gr_1_wall_redcorner", + "loc.poh_gr_1_wall_bluecorner", + ), + built = + listOf( + "loc.poh_fencing_ring_mat_middle", + "loc.poh_fencing_ring_mat_side", + "loc.poh_fencing_ring_mat_side", + "loc.poh_fencing_ring_mat_side", + "loc.poh_fencing_ring_mat_corner", + "loc.poh_fencing_ring_mat_corner", + "loc.poh_fencing_ring_mat_corner", + "loc.poh_fencing_ring_mat_corner", + "loc.poh_fencing_ringwall", + "loc.poh_fencing_ringwall", + "loc.poh_fencing_ringwall", + "loc.poh_fencing_ringwall", + "loc.poh_fencing_ringwall", + "loc.poh_fencing_ringwall", + "loc.poh_fencing_ringwall", + "loc.poh_fencing_ringwall", + ), + groupBuild = true, + ) { + material("obj.plank_oak", 8) + material("obj.cloth", 6) + } + } + row("dbrow.poh_combat_ring") { + option( + "Combat ring", + level = 51, + xp = 630.0, + hotspots = + listOf( + "loc.poh_gr_1_floor_middle", + "loc.poh_gr_1_floor_side", + "loc.poh_gr_1_floor_n", + "loc.poh_gr_1_floor_s", + "loc.poh_gr_1_floor_ne", + "loc.poh_gr_1_floor_nw", + "loc.poh_gr_1_floor_se", + "loc.poh_gr_1_floor_sw", + "loc.poh_gr_1_wall_combat", + "loc.poh_gr_1_wall_combat_agility", + "loc.poh_gr_1_wall_ranging_combat", + "loc.poh_gr_1_wall_everything", + "loc.poh_gr_1_wall_cobat_corner", + "loc.poh_gr_1_wall_combat_agility_corner", + "loc.poh_gr_1_wall_redcorner", + "loc.poh_gr_1_wall_bluecorner", + ), + built = + listOf( + "loc.poh_combat_mat_middle", + "loc.poh_combat_mat_side", + "loc.poh_combat_mat_side", + "loc.poh_combat_mat_side", + "loc.poh_combat_mat_corner", + "loc.poh_combat_mat_corner", + "loc.poh_combat_mat_corner", + "loc.poh_combat_mat_corner", + "loc.poh_combat_ringwall", + "loc.poh_combat_ringwall", + "loc.poh_combat_ringwall", + "loc.poh_combat_ringwall", + "loc.poh_combat_ringwall", + "loc.poh_combat_ringwall", + "loc.poh_combat_ringwall", + "loc.poh_combat_ringwall", + ), + groupBuild = true, + ) { + material("obj.plank_teak", 6) + material("obj.cloth", 6) + } + } + row("dbrow.poh_ranging_pedestals") { + option( + "Ranging pedestals", + level = 71, + xp = 720.0, + hotspots = + listOf( + "loc.poh_gr_1_floor_middle", + "loc.poh_gr_1_floor_side", + "loc.poh_gr_1_floor_n", + "loc.poh_gr_1_floor_s", + "loc.poh_gr_1_floor_ne", + "loc.poh_gr_1_floor_nw", + "loc.poh_gr_1_floor_se", + "loc.poh_gr_1_floor_sw", + "loc.poh_gr_1_wall_ranging", + "loc.poh_gr_1_wall_ranging_agility", + "loc.poh_gr_1_wall_ranging_combat", + "loc.poh_gr_1_wall_everything", + ), + built = + listOf( + "loc.poh_magic_circle_mat", + "loc.poh_magic_circle_mat", + "loc.poh_magic_circle_mat", + "loc.poh_magic_circle_mat", + "loc.poh_magic_circle_mat", + "loc.poh_magic_circle_mat", + "loc.poh_magic_circle_mat", + "loc.poh_magic_circle_mat", + "loc.poh_magic_circle_wall", + "loc.poh_magic_circle_wall", + "loc.poh_magic_circle_wall", + "loc.poh_magic_circle_wall", + ), + groupBuild = true, + ) { + material("obj.plank_teak", 8) + } + } + row("dbrow.poh_boxing_glove_rack") { + option( + "Boxing glove rack", + level = 34, + xp = 120.0, + hotspots = listOf("loc.poh_combat_room_4"), + built = listOf("loc.poh_weapons_rack_gloves"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_weapons_rack") { + option( + "Weapons rack", + level = 44, + xp = 180.0, + hotspots = listOf("loc.poh_combat_room_4"), + built = listOf("loc.poh_weapons_rack_gloves+woodenstuff"), + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_extra_weapons_rack") { + option( + "Extra weapons rack", + level = 54, + xp = 440.0, + hotspots = listOf("loc.poh_combat_room_4"), + built = listOf("loc.poh_weapons_rack_gloves+woodenstuff+pugels"), + ) { + material("obj.plank_teak", 4) + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_combat_dummy_row") { + option( + "Combat dummy", + level = 48, + xp = 660.0, + hotspots = listOf("loc.poh_combat_room_6"), + built = listOf("loc.poh_combat_dummy"), + ) { + material("obj.plank_teak", 5) + material("obj.cloth", 4) + material("obj.bucket_sand", 5) + } + } + row("dbrow.poh_undead_combat_dummy") { + option( + "Undead combat dummy", + level = 53, + xp = 220.0, + hotspots = listOf("loc.poh_combat_dummy"), + built = listOf("loc.poh_combat_dummy_undeadslayer"), + ) { + material("obj.harmless_black_mask", 1) + material("obj.bucket_ectoplasm", 4) + } + } + row("dbrow.poh_ornate_undead_combat_dummy") { + option( + "Ornate undead combat dummy", + level = 58, + xp = 300.0, + hotspots = listOf("loc.poh_combat_dummy_undeadslayer"), + built = listOf("loc.poh_combat_dummy_upgraded_1_undead"), + ) { + material("obj.gold_leaf", 1) + } + } + + /* Portal chamber - directing a frame to a destination is runtime state. */ + // TODO(unmatched): Raging echoes portal - cosmetic unlock scroll obj absent from rev 240. + row("dbrow.poh_teak_portal") { + option( + "Teak portal", + level = 50, + xp = 270.0, + hotspots = + listOf( + "loc.poh_teleroom_1", + "loc.poh_teleroom_2", + "loc.poh_teleroom_3", + ), + built = + listOf( + "loc.poh_portal_teak_empty", + "loc.poh_portal_teak_empty", + "loc.poh_portal_teak_empty", + ), + ) { + material("obj.plank_teak", 3) + } + } + row("dbrow.poh_mahogany_portal") { + option( + "Mahogany portal", + level = 65, + xp = 420.0, + hotspots = + listOf( + "loc.poh_teleroom_1", + "loc.poh_teleroom_2", + "loc.poh_teleroom_3", + ), + built = + listOf( + "loc.poh_portal_mag_empty", + "loc.poh_portal_mag_empty", + "loc.poh_portal_mag_empty", + ), + ) { + material("obj.plank_mahogany", 3) + } + } + row("dbrow.poh_marble_portal") { + option( + "Marble portal", + level = 80, + xp = 1500.0, + hotspots = + listOf( + "loc.poh_teleroom_1", + "loc.poh_teleroom_2", + "loc.poh_teleroom_3", + ), + built = + listOf( + "loc.poh_portal_marble_empty", + "loc.poh_portal_marble_empty", + "loc.poh_portal_marble_empty", + ), + ) { + material("obj.marble_block", 3) + } + } + row("dbrow.poh_teleport_focus") { + option( + "Teleport focus", + level = 50, + xp = 40.0, + hotspots = listOf("loc.poh_teleroom_7"), + built = listOf("loc.poh_teleport_centrepiece"), + ) { + material("obj.limestonebrick", 2) + } + } + row("dbrow.poh_greater_teleport_focus") { + option( + "Greater teleport focus", + level = 65, + xp = 500.0, + hotspots = listOf("loc.poh_teleroom_7"), + built = listOf("loc.poh_teleport_centrepiece_grand"), + ) { + material("obj.marble_block", 1) + } + } + row("dbrow.poh_scrying_pool") { + option( + "Scrying pool", + level = 80, + xp = 2000.0, + hotspots = listOf("loc.poh_teleroom_7"), + built = listOf("loc.poh_scrying_pool"), + ) { + material("obj.marble_block", 4) + } + } + + /* Costume room - tiers above oak upgrade the previous built case/rack/box. */ + row("dbrow.poh_oak_armour_case") { + option( + "Oak armour case", + level = 46, + xp = 180.0, + hotspots = listOf("loc.poh_cos_room_armour_case_hotspot"), + built = listOf("loc.poh_cos_room_armour_case_oak"), + flatpack = "obj.poh_flatpack_armour_case", + ) { + material("obj.plank_oak", 3) + } + } + row("dbrow.poh_teak_armour_case") { + option( + "Teak armour case", + level = 64, + xp = 270.0, + hotspots = listOf("loc.poh_cos_room_armour_case_oak"), + built = listOf("loc.poh_cos_room_armour_case_teak"), + flatpack = "obj.poh_flatpack_armour_case2", + ) { + material("obj.plank_teak", 3) + } + } + row("dbrow.poh_mahogany_armour_case") { + option( + "Mahogany armour case", + level = 82, + xp = 420.0, + hotspots = listOf("loc.poh_cos_room_armour_case_teak"), + built = listOf("loc.poh_cos_room_armour_case_mahogany"), + flatpack = "obj.poh_flatpack_armour_case3", + ) { + material("obj.plank_mahogany", 3) + } + } + row("dbrow.poh_oak_cape_rack") { + option( + "Oak cape rack", + level = 54, + xp = 240.0, + hotspots = listOf("loc.poh_cos_room_cape_rack_hotspot"), + built = listOf("loc.poh_cos_room_cape_rack_oak"), + flatpack = "obj.poh_flatpack_cape_rack", + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_teak_cape_rack") { + option( + "Teak cape rack", + level = 63, + xp = 360.0, + hotspots = listOf("loc.poh_cos_room_cape_rack_oak"), + built = listOf("loc.poh_cos_room_cape_rack_teak"), + flatpack = "obj.poh_flatpack_cape_rack2", + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_mahogany_cape_rack") { + option( + "Mahogany cape rack", + level = 72, + xp = 560.0, + hotspots = listOf("loc.poh_cos_room_cape_rack_teak"), + built = listOf("loc.poh_cos_room_cape_rack_mahogany"), + flatpack = "obj.poh_flatpack_cape_rack3", + ) { + material("obj.plank_mahogany", 4) + } + } + row("dbrow.poh_gilded_cape_rack") { + option( + "Gilded cape rack", + level = 81, + xp = 860.0, + hotspots = listOf("loc.poh_cos_room_cape_rack_mahogany"), + built = listOf("loc.poh_cos_room_cape_rack_mahogany_gilded"), + flatpack = "obj.poh_flatpack_cape_rack4", + ) { + material("obj.plank_mahogany", 4) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_marble_cape_rack") { + option( + "Marble cape rack", + level = 90, + xp = 500.0, + hotspots = listOf("loc.poh_cos_room_cape_rack_mahogany_gilded"), + built = listOf("loc.poh_cos_room_cape_rack_marble"), + flatpack = "obj.poh_flatpack_cape_rack5", + ) { + material("obj.marble_block", 1) + } + } + row("dbrow.poh_magical_cape_rack") { + option( + "Magical cape rack", + level = 99, + xp = 1000.0, + hotspots = listOf("loc.poh_cos_room_cape_rack_marble"), + built = listOf("loc.poh_cos_room_cape_rack_magic_stone"), + flatpack = "obj.poh_flatpack_cape_rack6", + ) { + material("obj.poh_magic_crystal", 1) + } + } + row("dbrow.poh_oak_fancy_dress_box") { + option( + "Oak fancy dress box", + level = 44, + xp = 120.0, + hotspots = listOf("loc.poh_cos_room_fancy_dress_box_hotspot"), + built = listOf("loc.poh_cos_room_fancy_dress_box_oak"), + flatpack = "obj.poh_flatpack_fancy_dress_box", + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_teak_fancy_dress_box") { + option( + "Teak fancy dress box", + level = 62, + xp = 180.0, + hotspots = listOf("loc.poh_cos_room_fancy_dress_box_oak"), + built = listOf("loc.poh_cos_room_fancy_dress_box_teak"), + flatpack = "obj.poh_flatpack_fancy_dress_box2", + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_mahogany_fancy_dress_box") { + option( + "Mahogany fancy dress box", + level = 80, + xp = 280.0, + hotspots = listOf("loc.poh_cos_room_fancy_dress_box_teak"), + built = listOf("loc.poh_cos_room_fancy_dress_box_mahogany"), + flatpack = "obj.poh_flatpack_fancy_dress_box3", + ) { + material("obj.plank_mahogany", 2) + } + } + row("dbrow.poh_oak_magic_wardrobe") { + option( + "Oak magic wardrobe", + level = 42, + xp = 240.0, + hotspots = listOf("loc.poh_cos_room_magic_wardrobe_hotspot"), + built = listOf("loc.poh_cos_room_magic_wardrobe_oak"), + flatpack = "obj.poh_flatpack_magic_wardrobe", + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_carved_oak_magic_wardrobe") { + option( + "Carved oak magic wardrobe", + level = 51, + xp = 360.0, + hotspots = listOf("loc.poh_cos_room_magic_wardrobe_oak"), + built = listOf("loc.poh_cos_room_magic_wardrobe_carved_oak"), + flatpack = "obj.poh_flatpack_magic_wardrobe2", + ) { + material("obj.plank_oak", 6) + } + } + row("dbrow.poh_teak_magic_wardrobe") { + option( + "Teak magic wardrobe", + level = 60, + xp = 360.0, + hotspots = listOf("loc.poh_cos_room_magic_wardrobe_carved_oak"), + built = listOf("loc.poh_cos_room_magic_wardrobe_teak"), + flatpack = "obj.poh_flatpack_magic_wardrobe3", + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_carved_teak_magic_wardrobe") { + option( + "Carved teak magic wardrobe", + level = 69, + xp = 540.0, + hotspots = listOf("loc.poh_cos_room_magic_wardrobe_teak"), + built = listOf("loc.poh_cos_room_magic_wardrobe_carved_teak"), + flatpack = "obj.poh_flatpack_magic_wardrobe4", + ) { + material("obj.plank_teak", 6) + } + } + row("dbrow.poh_mahogany_magic_wardrobe") { + option( + "Mahogany magic wardrobe", + level = 78, + xp = 560.0, + hotspots = listOf("loc.poh_cos_room_magic_wardrobe_carved_teak"), + built = listOf("loc.poh_cos_room_magic_wardrobe_mahogany"), + flatpack = "obj.poh_flatpack_magic_wardrobe5", + ) { + material("obj.plank_mahogany", 4) + } + } + row("dbrow.poh_gilded_magic_wardrobe") { + option( + "Gilded magic wardrobe", + level = 87, + xp = 860.0, + hotspots = listOf("loc.poh_cos_room_magic_wardrobe_mahogany"), + built = listOf("loc.poh_cos_room_magic_wardrobe_mahogany_gilded"), + flatpack = "obj.poh_flatpack_magic_wardrobe6", + ) { + material("obj.plank_mahogany", 4) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_marble_magic_wardrobe") { + option( + "Marble magic wardrobe", + level = 96, + xp = 500.0, + hotspots = listOf("loc.poh_cos_room_magic_wardrobe_mahogany_gilded"), + built = listOf("loc.poh_cos_room_magic_wardrobe_marble"), + flatpack = "obj.poh_flatpack_magic_wardrobe7", + ) { + material("obj.marble_block", 1) + } + } + row("dbrow.poh_oak_toy_box") { + option( + "Oak toy box", + level = 50, + xp = 120.0, + hotspots = listOf("loc.poh_cos_room_toy_box_hotspot"), + built = listOf("loc.poh_cos_room_toy_box_oak"), + flatpack = "obj.poh_flatpack_toy_box", + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_teak_toy_box") { + option( + "Teak toy box", + level = 68, + xp = 180.0, + hotspots = listOf("loc.poh_cos_room_toy_box_hotspot"), + built = listOf("loc.poh_cos_room_toy_box_teak"), + flatpack = "obj.poh_flatpack_toy_box2", + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_mahogany_toy_box") { + option( + "Mahogany toy box", + level = 86, + xp = 280.0, + hotspots = listOf("loc.poh_cos_room_toy_box_hotspot"), + built = listOf("loc.poh_cos_room_toy_box_mahogany"), + flatpack = "obj.poh_flatpack_toy_box3", + ) { + material("obj.plank_mahogany", 2) + } + } + row("dbrow.poh_oak_treasure_chest") { + option( + "Oak treasure chest", + level = 48, + xp = 120.0, + hotspots = listOf("loc.poh_cos_room_tresure_chest_hotspot"), + built = listOf("loc.poh_cos_room_tresure_chest_oak"), + flatpack = "obj.poh_flatpack_treasure_chest", + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_teak_treasure_chest") { + option( + "Teak treasure chest", + level = 66, + xp = 180.0, + hotspots = listOf("loc.poh_cos_room_tresure_chest_oak"), + built = listOf("loc.poh_cos_room_tresure_chest_teak"), + flatpack = "obj.poh_flatpack_treasure_chest2", + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_mahogany_treasure_chest") { + option( + "Mahogany treasure chest", + level = 84, + xp = 280.0, + hotspots = listOf("loc.poh_cos_room_tresure_chest_teak"), + built = listOf("loc.poh_cos_room_tresure_chest_mahogany"), + flatpack = "obj.poh_flatpack_treasure_chest3", + ) { + material("obj.plank_mahogany", 2) + } + } + + /* Menagerie (indoor and outdoor share hotspot locs). */ + row("dbrow.poh_simple_arena") { + option( + "Simple arena", + level = 63, + xp = 139.0, + hotspots = + listOf( + "loc.poh_menagerie_combatring_hotspot", + "loc.poh_menagerie_combatring_mat_hotspot", + ), + built = + listOf( + "loc.poh_menagerie_combatring_1", + "loc.poh_menagerie_combatring_mat", + ), + groupBuild = true, + ) { + material("obj.plank_oak", 2) + material("obj.cloth", 1) + material("obj.rope", 1) + } + } + row("dbrow.poh_advanced_arena") { + option( + "Advanced arena", + level = 73, + xp = 199.0, + hotspots = + listOf( + "loc.poh_menagerie_combatring_hotspot", + "loc.poh_menagerie_combatring_mat_hotspot", + ), + built = + listOf( + "loc.poh_menagerie_combatring_2", + "loc.poh_menagerie_combatring_mat", + ), + groupBuild = true, + ) { + material("obj.plank_teak", 2) + material("obj.cloth", 1) + material("obj.rope", 1) + } + } + row("dbrow.poh_glorious_arena") { + option( + "Glorious arena", + level = 83, + xp = 299.0, + hotspots = + listOf( + "loc.poh_menagerie_combatring_hotspot", + "loc.poh_menagerie_combatring_mat_hotspot", + ), + built = + listOf( + "loc.poh_menagerie_combatring_3", + "loc.poh_menagerie_combatring_mat", + ), + groupBuild = true, + ) { + material("obj.plank_mahogany", 2) + material("obj.cloth", 1) + material("obj.rope", 1) + } + } + row("dbrow.poh_grassland_habitat") { + option( + "Grassland habitat", + level = 37, + xp = 37.0, + hotspots = + listOf( + "loc.poh_menagerie_habitat_ground_corner", + "loc.poh_menagerie_habitat_ground_side", + "loc.poh_menagerie_habitat_ground_middle", + "loc.poh_menagerie_habitat_feature", + ), + built = + listOf( + "loc.poh_menagerie_habitat_ground_corner_1", + "loc.poh_menagerie_habitat_ground_side_1", + "loc.poh_menagerie_habitat_ground_middle_1", + "loc.poh_menagerie_habitat_feature_1", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_tree_1", 1) + material("obj.bucket_compost", 2) + } + } + row("dbrow.poh_forest_habitat") { + option( + "Forest habitat", + level = 47, + xp = 51.0, + hotspots = + listOf( + "loc.poh_menagerie_habitat_ground_corner", + "loc.poh_menagerie_habitat_ground_side", + "loc.poh_menagerie_habitat_ground_middle", + "loc.poh_menagerie_habitat_feature", + ), + built = + listOf( + "loc.poh_menagerie_habitat_ground_corner_2", + "loc.poh_menagerie_habitat_ground_side_2", + "loc.poh_menagerie_habitat_ground_middle_2", + "loc.poh_menagerie_habitat_feature_2", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_tree_2", 1) + material("obj.bucket_compost", 3) + } + } + row("dbrow.poh_desert_habitat") { + option( + "Desert habitat", + level = 57, + xp = 181.0, + hotspots = + listOf( + "loc.poh_menagerie_habitat_ground_corner", + "loc.poh_menagerie_habitat_ground_side", + "loc.poh_menagerie_habitat_ground_middle", + "loc.poh_menagerie_habitat_feature", + ), + built = + listOf( + "loc.poh_menagerie_habitat_ground_corner_3", + "loc.poh_menagerie_habitat_ground_side_3", + "loc.poh_menagerie_habitat_ground_middle_3", + "loc.poh_menagerie_habitat_feature_3", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_plant_1", 1) + material("obj.bucket_sand", 5) + } + } + row("dbrow.poh_polar_habitat") { + option( + "Polar habitat", + level = 67, + xp = 271.0, + hotspots = + listOf( + "loc.poh_menagerie_habitat_ground_corner", + "loc.poh_menagerie_habitat_ground_side", + "loc.poh_menagerie_habitat_ground_middle", + "loc.poh_menagerie_habitat_feature", + ), + built = + listOf( + "loc.poh_menagerie_habitat_ground_corner_4", + "loc.poh_menagerie_habitat_ground_side_4", + "loc.poh_menagerie_habitat_ground_middle_4", + "loc.poh_menagerie_habitat_feature_4", + ), + groupBuild = true, + ) { + material("obj.plank_oak", 3) + material("obj.waterrune", 2000) + material("obj.slayer_icy_water", 5) + } + } + row("dbrow.poh_volcanic_habitat") { + option( + "Volcanic habitat", + level = 77, + xp = 46.0, + hotspots = + listOf( + "loc.poh_menagerie_habitat_ground_corner", + "loc.poh_menagerie_habitat_ground_side", + "loc.poh_menagerie_habitat_ground_middle", + "loc.poh_menagerie_habitat_feature", + ), + built = + listOf( + "loc.poh_menagerie_habitat_ground_corner_5", + "loc.poh_menagerie_habitat_ground_side_5", + "loc.poh_menagerie_habitat_ground_middle_5", + "loc.poh_menagerie_habitat_feature_5", + ), + groupBuild = true, + ) { + material("obj.enakh_granite_medium", 5) + material("obj.lavarune", 100) + } + } + row("dbrow.poh_oak_feeder") { + option( + "Oak feeder", + level = 37, + xp = 182.0, + hotspots = listOf("loc.poh_menagerie_petfeeder_hotspot"), + built = listOf("loc.poh_menagerie_petfeeder_1"), + ) { + material("obj.plank_oak", 3) + material("obj.bucket_milk", 1) + } + } + row("dbrow.poh_teak_feeder") { + option( + "Teak feeder", + level = 48, + xp = 272.0, + hotspots = listOf("loc.poh_menagerie_petfeeder_hotspot"), + built = listOf("loc.poh_menagerie_petfeeder_2"), + ) { + material("obj.plank_teak", 3) + material("obj.bucket_milk", 1) + } + } + row("dbrow.poh_mahogany_feeder") { + option( + "Mahogany feeder", + level = 59, + xp = 862.0, + hotspots = listOf("loc.poh_menagerie_petfeeder_hotspot"), + built = listOf("loc.poh_menagerie_petfeeder_3"), + ) { + material("obj.plank_mahogany", 4) + material("obj.bucket_milk", 1) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_oak_pet_house") { + option( + "Oak house", + level = 37, + xp = 240.0, + hotspots = listOf("loc.poh_menagerie_pethouse_hotspot"), + built = listOf("loc.poh_menagerie_pethouse_1"), + ) { + material("obj.plank_oak", 4) + } + } + row("dbrow.poh_teak_pet_house") { + option( + "Teak house", + level = 48, + xp = 360.0, + hotspots = listOf("loc.poh_menagerie_pethouse_1"), + built = listOf("loc.poh_menagerie_pethouse_2"), + ) { + material("obj.plank_teak", 4) + } + } + row("dbrow.poh_mahogany_pet_house") { + option( + "Mahogany house", + level = 59, + xp = 560.0, + hotspots = listOf("loc.poh_menagerie_pethouse_2"), + built = listOf("loc.poh_menagerie_pethouse_3"), + ) { + material("obj.plank_mahogany", 4) + } + } + row("dbrow.poh_consecrated_pet_house") { + option( + "Consecrated house", + level = 70, + xp = 1560.0, + hotspots = listOf("loc.poh_menagerie_pethouse_3"), + built = listOf("loc.poh_menagerie_pethouse_4"), + ) { + material("obj.plank_mahogany", 4) + material("obj.poh_magic_crystal", 1) + } + } + row("dbrow.poh_desecrated_pet_house") { + option( + "Desecrated house", + level = 81, + xp = 160.0, + hotspots = listOf("loc.poh_menagerie_pethouse_4"), + built = listOf("loc.poh_menagerie_pethouse_5"), + ) { + material("obj.plank_mahogany", 1) + material("obj.limestonebrick", 1) + } + } + row("dbrow.poh_nature_pet_house") { + option( + "Nature house", + level = 92, + xp = 158.0, + hotspots = listOf("loc.poh_menagerie_pethouse_5"), + built = listOf("loc.poh_menagerie_pethouse_6"), + ) { + material("obj.plank_mahogany", 1) + material("obj.bucket_water", 2) + material("obj.bucket_supercompost", 3) + } + } + row("dbrow.poh_pet_list") { + option( + "Pet list", + level = 38, + xp = 198.0, + hotspots = listOf("loc.poh_menagerie_petlist_hotspot"), + built = listOf("loc.poh_menagerie_petlist_1"), + ) { + material("obj.plank_oak", 3) + material("obj.cloth", 1) + material("obj.papyrus", 1) + } + } + row("dbrow.poh_oak_scratching_post") { + option( + "Oak scratching post", + level = 39, + xp = 124.0, + hotspots = listOf("loc.poh_menagerie_scratchingpost_hotspot"), + built = listOf("loc.poh_menagerie_scratchingpost_1"), + ) { + material("obj.plank_oak", 2) + material("obj.rope", 1) + } + } + row("dbrow.poh_teak_scratching_post") { + option( + "Teak scratching post", + level = 49, + xp = 204.0, + hotspots = listOf("loc.poh_menagerie_scratchingpost_hotspot"), + built = listOf("loc.poh_menagerie_scratchingpost_2"), + ) { + material("obj.plank_teak", 2) + material("obj.rope", 1) + material("obj.limestonebrick", 1) + } + } + row("dbrow.poh_mahogany_scratching_post") { + option( + "Mahogany scratching post", + level = 59, + xp = 304.0, + hotspots = listOf("loc.poh_menagerie_scratchingpost_hotspot"), + built = listOf("loc.poh_menagerie_scratchingpost_3"), + ) { + material("obj.plank_mahogany", 2) + material("obj.rope", 1) + material("obj.limestonebrick", 1) + } + } + + /* Dungeon - guards/traps/doors/lighting/decoration shared by corridor, junction, stairs, + treasure room and oubliette. Guards, traps and monsters are paid for in coins. */ + row("dbrow.poh_dungeon_skeleton_guard") { + option( + "Skeleton guard", + level = 70, + xp = 223.0, + hotspots = + listOf( + "loc.poh_dungeon_1", + "loc.poh_dungeon_stairs_2", + "loc.poh_dungeon_stairs_3", + "loc.poh_oubliette_3", + ), + built = + listOf( + "loc.poh_skeleton", + "loc.poh_skeleton", + "loc.poh_skeleton", + "loc.poh_skeleton", + ), + ) { + material("obj.coins", 50000) + } + } + row("dbrow.poh_dungeon_guard_dog") { + option( + "Guard dog", + level = 74, + xp = 273.0, + hotspots = + listOf( + "loc.poh_dungeon_1", + "loc.poh_dungeon_stairs_2", + "loc.poh_dungeon_stairs_3", + "loc.poh_oubliette_3", + ), + built = + listOf( + "loc.poh_guarddog", + "loc.poh_guarddog", + "loc.poh_guarddog", + "loc.poh_guarddog", + ), + ) { + material("obj.coins", 75000) + } + } + row("dbrow.poh_hobgoblin_guard") { + option( + "Hobgoblin", + level = 78, + xp = 316.0, + hotspots = + listOf( + "loc.poh_dungeon_1", + "loc.poh_dungeon_stairs_2", + "loc.poh_dungeon_stairs_3", + "loc.poh_oubliette_3", + ), + built = + listOf( + "loc.poh_hobgoblin", + "loc.poh_hobgoblin", + "loc.poh_hobgoblin", + "loc.poh_hobgoblin", + ), + ) { + material("obj.coins", 100000) + } + } + row("dbrow.poh_baby_red_dragon") { + option( + "Baby red dragon", + level = 82, + xp = 387.0, + hotspots = + listOf( + "loc.poh_dungeon_1", + "loc.poh_dungeon_stairs_2", + "loc.poh_dungeon_stairs_3", + "loc.poh_oubliette_3", + ), + built = + listOf( + "loc.poh_babyreddragon", + "loc.poh_babyreddragon", + "loc.poh_babyreddragon", + "loc.poh_babyreddragon", + ), + ) { + material("obj.coins", 150000) + } + } + row("dbrow.poh_huge_spider") { + option( + "Huge spider", + level = 86, + xp = 447.0, + hotspots = + listOf( + "loc.poh_dungeon_1", + "loc.poh_dungeon_stairs_2", + "loc.poh_dungeon_stairs_3", + "loc.poh_oubliette_3", + ), + built = + listOf( + "loc.poh_giantspider", + "loc.poh_giantspider", + "loc.poh_giantspider", + "loc.poh_giantspider", + ), + ) { + material("obj.coins", 200000) + } + } + row("dbrow.poh_troll_guard") { + option( + "Troll guard", + level = 90, + xp = 1000.0, + hotspots = + listOf( + "loc.poh_dungeon_1", + "loc.poh_dungeon_stairs_2", + "loc.poh_dungeon_stairs_3", + "loc.poh_oubliette_3", + ), + built = + listOf( + "loc.poh_troll", + "loc.poh_troll", + "loc.poh_troll", + "loc.poh_troll", + ), + ) { + material("obj.coins", 1000000) + } + } + row("dbrow.poh_hellhound_guard") { + option( + "Hellhound", + level = 94, + xp = 2236.0, + hotspots = + listOf( + "loc.poh_dungeon_1", + "loc.poh_dungeon_stairs_2", + "loc.poh_dungeon_stairs_3", + "loc.poh_oubliette_3", + ), + built = + listOf( + "loc.poh_hellhound", + "loc.poh_hellhound", + "loc.poh_hellhound", + "loc.poh_hellhound", + ), + ) { + material("obj.coins", 5000000) + } + } + row("dbrow.poh_dungeon_spike_trap") { + option( + "Spike trap", + level = 72, + xp = 223.0, + hotspots = listOf("loc.poh_dungeon_2", "loc.poh_dungeon_3"), + built = listOf("loc.poh_trap_1", "loc.poh_trap_1"), + ) { + material("obj.coins", 50000) + } + } + row("dbrow.poh_dungeon_man_trap") { + option( + "Man trap", + level = 76, + xp = 273.0, + hotspots = listOf("loc.poh_dungeon_2", "loc.poh_dungeon_3"), + built = listOf("loc.poh_trap_2", "loc.poh_trap_2"), + ) { + material("obj.coins", 75000) + } + } + row("dbrow.poh_tangle_vine") { + option( + "Tangle vine", + level = 80, + xp = 316.0, + hotspots = listOf("loc.poh_dungeon_2", "loc.poh_dungeon_3"), + built = listOf("loc.poh_trap_3", "loc.poh_trap_3"), + ) { + material("obj.coins", 100000) + } + } + row("dbrow.poh_dungeon_marble_trap") { + option( + "Marble trap", + level = 84, + xp = 387.0, + hotspots = listOf("loc.poh_dungeon_2", "loc.poh_dungeon_3"), + built = listOf("loc.poh_trap_4", "loc.poh_trap_4"), + ) { + material("obj.coins", 150000) + } + } + row("dbrow.poh_dungeon_teleport_trap") { + option( + "Teleport trap", + level = 88, + xp = 447.0, + hotspots = listOf("loc.poh_dungeon_2", "loc.poh_dungeon_3"), + built = listOf("loc.poh_trap_5", "loc.poh_trap_5"), + ) { + material("obj.coins", 200000) + } + } + row("dbrow.poh_oak_door") { + option( + "Oak door", + level = 74, + xp = 600.0, + hotspots = listOf("loc.poh_dungeon_4l", "loc.poh_dungeon_4r"), + built = listOf("loc.poh_dungeon_ldoor_oak", "loc.poh_dungeon_rdoor_oak"), + groupBuild = true, + ) { + material("obj.plank_oak", 10) + } + } + row("dbrow.poh_oak_door_2") { + option( + "Oak door", + level = 74, + xp = 600.0, + hotspots = listOf("loc.poh_dungeon_5l", "loc.poh_dungeon_5r"), + built = listOf("loc.poh_dungeon_ldoor_oak", "loc.poh_dungeon_rdoor_oak"), + groupBuild = true, + ) { + material("obj.plank_oak", 10) + } + } + row("dbrow.poh_steel_plated_door") { + option( + "Steel-plated door", + level = 84, + xp = 800.0, + hotspots = listOf("loc.poh_dungeon_4l", "loc.poh_dungeon_4r"), + built = listOf("loc.poh_dungeon_ldoor_steel", "loc.poh_dungeon_rdoor_steel"), + groupBuild = true, + ) { + material("obj.plank_oak", 10) + material("obj.steel_bar", 10) + } + } + row("dbrow.poh_steel_plated_door_2") { + option( + "Steel-plated door", + level = 84, + xp = 800.0, + hotspots = listOf("loc.poh_dungeon_5l", "loc.poh_dungeon_5r"), + built = listOf("loc.poh_dungeon_ldoor_steel", "loc.poh_dungeon_rdoor_steel"), + groupBuild = true, + ) { + material("obj.plank_oak", 10) + material("obj.steel_bar", 10) + } + } + row("dbrow.poh_marble_door") { + option( + "Marble door", + level = 94, + xp = 2000.0, + hotspots = listOf("loc.poh_dungeon_4l", "loc.poh_dungeon_4r"), + built = listOf("loc.poh_dungeon_ldoor_marble", "loc.poh_dungeon_rdoor_marble"), + groupBuild = true, + ) { + material("obj.marble_block", 4) + } + } + row("dbrow.poh_marble_door_2") { + option( + "Marble door", + level = 94, + xp = 2000.0, + hotspots = listOf("loc.poh_dungeon_5l", "loc.poh_dungeon_5r"), + built = listOf("loc.poh_dungeon_ldoor_marble", "loc.poh_dungeon_rdoor_marble"), + groupBuild = true, + ) { + material("obj.marble_block", 4) + } + } + row("dbrow.poh_dungeon_candles") { + option( + "Candle", + level = 72, + xp = 243.0, + hotspots = listOf("loc.poh_dungeon_6", "loc.poh_oubliette_4"), + built = listOf("loc.poh_dungeon_candle", "loc.poh_dungeon_candle"), + groupBuild = true, + ) { + material("obj.plank_oak", 4) + material("obj.lit_candle", 4) + } + } + row("dbrow.poh_dungeon_torches") { + option( + "Torches", + level = 84, + xp = 244.0, + hotspots = listOf("loc.poh_dungeon_6", "loc.poh_oubliette_4"), + built = listOf("loc.poh_dungeon_torch", "loc.poh_dungeon_torch"), + groupBuild = true, + ) { + material("obj.plank_oak", 4) + material("obj.torch_lit", 4) + } + } + row("dbrow.poh_dungeon_skull_torches") { + option( + "Skull torches", + level = 94, + xp = 246.0, + hotspots = listOf("loc.poh_dungeon_6", "loc.poh_oubliette_4"), + built = listOf("loc.poh_dungeon_skulltorch", "loc.poh_dungeon_skulltorch"), + groupBuild = true, + ) { + material("obj.plank_oak", 4) + material("obj.torch_lit", 4) + material("obj.skull", 4) + } + } + row("dbrow.poh_decorative_blood") { + option( + "Decorative blood", + level = 72, + xp = 4.0, + hotspots = listOf("loc.poh_dungeon_7"), + built = listOf("loc.poh_dungeon_walldecor_blood"), + groupBuild = true, + ) { + material("obj.reddye", 4) + } + } + row("dbrow.poh_decorative_pipe") { + option( + "Decorative pipe", + level = 83, + xp = 120.0, + hotspots = listOf("loc.poh_dungeon_7"), + built = listOf("loc.poh_dungeon_walldecor_pipe"), + groupBuild = true, + ) { + material("obj.steel_bar", 6) + } + } + row("dbrow.poh_hanging_skeleton") { + option( + "Hanging skeleton", + level = 94, + xp = 3.0, + hotspots = listOf("loc.poh_dungeon_7"), + built = listOf("loc.poh_dungeon_walldecor_skeleton"), + groupBuild = true, + ) { + material("obj.skull", 2) + material("obj.bones", 6) + } + } + + /* Oubliette. */ + row("dbrow.poh_oubliette_floor_spikes") { + option( + "Spikes", + level = 65, + xp = 623.0, + hotspots = + listOf( + "loc.poh_oubliette_1", + "loc.poh_oubliette_1_side", + "loc.poh_oubliette_1_corner", + ), + built = + listOf( + "loc.poh_oubliette_spikes_mid", + "loc.poh_oubliette_spikes_side", + "loc.poh_oubliette_spikes_corner", + ), + groupBuild = true, + ) { + material("obj.steel_bar", 20) + material("obj.coins", 50000) + } + } + row("dbrow.poh_tentacle_pool") { + option( + "Tentacle pool", + level = 71, + xp = 326.0, + hotspots = + listOf( + "loc.poh_oubliette_1", + "loc.poh_oubliette_1_side", + "loc.poh_oubliette_1_corner", + ), + built = + listOf( + "loc.poh_oubliette_pool_mid", + "loc.poh_oubliette_pool_side", + "loc.poh_oubliette_pool_corner", + ), + groupBuild = true, + ) { + material("obj.bucket_water", 20) + material("obj.coins", 100000) + } + } + row("dbrow.poh_flame_pit") { + option( + "Flame pit", + level = 77, + xp = 357.0, + hotspots = listOf("loc.poh_oubliette_1_type8"), + built = listOf("loc.poh_oubliette_floor_fire"), + groupBuild = true, + ) { + material("obj.tinderbox", 20) + material("obj.coins", 125000) + } + } + row("dbrow.poh_rocnar") { + option( + "Rocnar", + level = 83, + xp = 387.0, + hotspots = listOf("loc.poh_oubliette_1_type8_ogre"), + built = listOf("loc.poh_oub_monster1"), + groupBuild = true, + ) { + material("obj.coins", 150000) + } + } + row("dbrow.poh_oak_cage") { + option( + "Oak cage", + level = 65, + xp = 640.0, + hotspots = listOf("loc.poh_oubliette_2_front", "loc.poh_oubliette_2_door"), + built = listOf("loc.poh_cage_dungeon_oak", "loc.poh_cage_dungeon_oak_door"), + groupBuild = true, + ) { + material("obj.plank_oak", 10) + material("obj.steel_bar", 2) + } + } + row("dbrow.poh_oak_and_steel_cage") { + option( + "Oak and steel cage", + level = 70, + xp = 800.0, + hotspots = listOf("loc.poh_oubliette_2_front", "loc.poh_oubliette_2_door"), + built = + listOf( + "loc.poh_cage_dungeon_oak+steel", + "loc.poh_cage_dungeon_oak+steel_door", + ), + groupBuild = true, + ) { + material("obj.plank_oak", 10) + material("obj.steel_bar", 10) + } + } + row("dbrow.poh_oubliette_steel_cage") { + option( + "Steel cage", + level = 75, + xp = 400.0, + hotspots = listOf("loc.poh_oubliette_2_front", "loc.poh_oubliette_2_door"), + built = listOf("loc.poh_cage_dungeon_steel", "loc.poh_cage_dungeon_steel_door"), + groupBuild = true, + ) { + material("obj.steel_bar", 20) + } + } + row("dbrow.poh_spiked_cage") { + option( + "Spiked cage", + level = 80, + xp = 500.0, + hotspots = listOf("loc.poh_oubliette_2_front", "loc.poh_oubliette_2_door"), + built = + listOf( + "loc.poh_cage_dungeon_steel+spikes", + "loc.poh_cage_dungeon_steel+spikes_door", + ), + groupBuild = true, + ) { + material("obj.steel_bar", 25) + } + } + row("dbrow.poh_bone_cage") { + option( + "Bone cage", + level = 85, + xp = 603.0, + hotspots = listOf("loc.poh_oubliette_2_front", "loc.poh_oubliette_2_door"), + built = listOf("loc.poh_cage_dungeon_bones", "loc.poh_cage_dungeon_bones_door"), + groupBuild = true, + ) { + material("obj.plank_oak", 10) + material("obj.bones", 10) + } + } + row("dbrow.poh_oak_ladder") { + option( + "Oak ladder", + level = 68, + xp = 300.0, + hotspots = listOf("loc.poh_oubliette_5"), + built = listOf("loc.poh_dungeon_ladder_oak"), + ) { + material("obj.plank_oak", 5) + } + } + row("dbrow.poh_teak_ladder") { + option( + "Teak ladder", + level = 78, + xp = 450.0, + hotspots = listOf("loc.poh_oubliette_5"), + built = listOf("loc.poh_dungeon_ladder_teak"), + ) { + material("obj.plank_teak", 5) + } + } + row("dbrow.poh_mahogany_ladder") { + option( + "Mahogany ladder", + level = 88, + xp = 700.0, + hotspots = listOf("loc.poh_oubliette_5"), + built = listOf("loc.poh_dungeon_ladder_mag"), + ) { + material("obj.plank_mahogany", 5) + } + } + + /* Treasure room. */ + row("dbrow.poh_wooden_crate") { + option( + "Wooden crate", + level = 75, + xp = 143.0, + hotspots = listOf("loc.poh_dungeon_treasure_1"), + built = listOf("loc.poh_treasure_woodencrate"), + nails = 5, + ) { + material("obj.woodplank", 5) + } + } + row("dbrow.poh_oak_chest") { + option( + "Oak chest", + level = 79, + xp = 340.0, + hotspots = listOf("loc.poh_dungeon_treasure_1"), + built = listOf("loc.poh_treasure_oak_chest"), + ) { + material("obj.plank_oak", 5) + material("obj.steel_bar", 2) + } + } + row("dbrow.poh_teak_chest") { + option( + "Teak chest", + level = 83, + xp = 530.0, + hotspots = listOf("loc.poh_dungeon_treasure_1"), + built = listOf("loc.poh_treasure_teak_chest"), + ) { + material("obj.plank_teak", 5) + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_mahogany_chest") { + option( + "Mahogany chest", + level = 87, + xp = 1000.0, + hotspots = listOf("loc.poh_dungeon_treasure_1"), + built = listOf("loc.poh_treasure_mag_chest"), + ) { + material("obj.plank_mahogany", 5) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_magic_chest") { + option( + "Magic chest", + level = 91, + xp = 1500.0, + hotspots = listOf("loc.poh_dungeon_treasure_1"), + built = listOf("loc.poh_treasure_magic_chest"), + ) { + material("obj.poh_magic_crystal", 1) + } + } + row("dbrow.poh_treasure_demon") { + option( + "Demon", + level = 75, + xp = 707.0, + hotspots = listOf("loc.poh_dungeon_treasure_2"), + built = listOf("loc.poh_demon"), + ) { + material("obj.coins", 500000) + } + } + row("dbrow.poh_treasure_kalphite_soldier") { + option( + "Kalphite soldier", + level = 80, + xp = 866.0, + hotspots = listOf("loc.poh_dungeon_treasure_2"), + built = listOf("loc.poh_kalphite_soldier"), + ) { + material("obj.coins", 750000) + } + } + row("dbrow.poh_treasure_tok_xil") { + option( + "Tok-Xil", + level = 85, + xp = 2236.0, + hotspots = listOf("loc.poh_dungeon_treasure_2"), + built = listOf("loc.poh_tok_xil"), + ) { + material("obj.coins", 5000000) + } + } + row("dbrow.poh_treasure_dagannoth") { + option( + "Dagannoth", + level = 90, + xp = 2738.0, + hotspots = listOf("loc.poh_dungeon_treasure_2"), + built = listOf("loc.poh_dagganoth"), + ) { + material("obj.coins", 7500000) + } + } + row("dbrow.poh_treasure_steel_dragon") { + option( + "Steel dragon", + level = 95, + xp = 3162.0, + hotspots = listOf("loc.poh_dungeon_treasure_2"), + built = listOf("loc.poh_steel_dragon"), + ) { + material("obj.coins", 10000000) + } + } + row("dbrow.poh_treasure_rune_dragon") { + option( + "Rune dragon", + level = 99, + xp = 5000.0, + hotspots = listOf("loc.poh_dungeon_treasure_2"), + built = listOf("loc.poh_rune_dragon"), + ) { + material("obj.coins", 25000000) + } + } + + /* Skill hall and quest hall - rugs/stairs shared (the dungeon stairs room reuses the + poh_hall1_1 hotspots). Mounted head/fish trophies attach to displays at runtime. */ + row("dbrow.poh_hall_rug") { + option( + "Rug", + level = 13, + xp = 60.0, + hotspots = + listOf( + "loc.poh_hall1_1_corner", + "loc.poh_hall1_1_side", + "loc.poh_hall1_1_middle", + "loc.poh_hall2_1_corner", + "loc.poh_hall2_1_side", + "loc.poh_hall2_1_middle", + ), + built = + listOf( + "loc.poh_rugcorner2", + "loc.poh_rugside2", + "loc.poh_rugmiddle2", + "loc.poh_rugcorner2", + "loc.poh_rugside2", + "loc.poh_rugmiddle2", + ), + groupBuild = true, + ) { + material("obj.cloth", 4) + } + } + row("dbrow.poh_hall_opulent_rug") { + option( + "Opulent rug", + level = 65, + xp = 360.0, + hotspots = + listOf( + "loc.poh_hall1_1_corner", + "loc.poh_hall1_1_side", + "loc.poh_hall1_1_middle", + "loc.poh_hall2_1_corner", + "loc.poh_hall2_1_side", + "loc.poh_hall2_1_middle", + ), + built = + listOf( + "loc.poh_rugcorner3", + "loc.poh_rugside3", + "loc.poh_rugmiddle3", + "loc.poh_rugcorner3", + "loc.poh_rugside3", + "loc.poh_rugmiddle3", + ), + groupBuild = true, + ) { + material("obj.cloth", 4) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_oak_staircase") { + option( + "Oak staircase", + level = 27, + xp = 680.0, + hotspots = + listOf( + "loc.poh_hall1_1_stairs_up", + "loc.poh_hall1_1_stairs_top", + "loc.poh_hall2_1_stairs_up", + "loc.poh_hall2_1_stairs_top", + ), + built = + listOf( + "loc.poh_stairs_3", + "loc.poh_stairstop_3", + "loc.poh_stairs_3", + "loc.poh_stairstop_3", + ), + ) { + material("obj.plank_oak", 10) + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_teak_staircase") { + option( + "Teak staircase", + level = 48, + xp = 980.0, + hotspots = + listOf( + "loc.poh_hall1_1_stairs_up", + "loc.poh_hall1_1_stairs_top", + "loc.poh_hall2_1_stairs_up", + "loc.poh_hall2_1_stairs_top", + ), + built = + listOf( + "loc.poh_stairs_4", + "loc.poh_stairstop_4", + "loc.poh_stairs_4", + "loc.poh_stairstop_4", + ), + ) { + material("obj.plank_teak", 10) + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_limestone_spiral_staircase") { + option( + "Limestone spiral staircase", + level = 67, + xp = 1040.0, + hotspots = + listOf( + "loc.poh_hall1_1_stairs_up", + "loc.poh_hall1_1_stairs_top", + "loc.poh_hall2_1_stairs_up", + "loc.poh_hall2_1_stairs_top", + ), + built = + listOf( + "loc.poh_spiralstairs", + "loc.poh_spiralstairs", + "loc.poh_spiralstairs", + "loc.poh_spiralstairs", + ), + ) { + material("obj.plank_teak", 10) + material("obj.limestonebrick", 7) + } + } + row("dbrow.poh_marble_staircase") { + option( + "Marble staircase", + level = 82, + xp = 3200.0, + hotspots = + listOf( + "loc.poh_hall1_1_stairs_up", + "loc.poh_hall1_1_stairs_top", + "loc.poh_hall2_1_stairs_up", + "loc.poh_hall2_1_stairs_top", + ), + built = + listOf( + "loc.poh_stairs_5", + "loc.poh_stairstop_5", + "loc.poh_stairs_5", + "loc.poh_stairstop_5", + ), + ) { + material("obj.plank_mahogany", 5) + material("obj.marble_block", 5) + } + } + row("dbrow.poh_marble_spiral") { + option( + "Marble spiral", + level = 97, + xp = 4400.0, + hotspots = + listOf( + "loc.poh_hall1_1_stairs_up", + "loc.poh_hall1_1_stairs_top", + "loc.poh_hall2_1_stairs_up", + "loc.poh_hall2_1_stairs_top", + ), + built = + listOf( + "loc.poh_spiralstairs_2", + "loc.poh_spiralstairs_2", + "loc.poh_spiralstairs_2", + "loc.poh_spiralstairs_2", + ), + ) { + material("obj.plank_teak", 10) + material("obj.marble_block", 7) + } + } + row("dbrow.poh_teak_head_display") { + option( + "Teak display", + level = 38, + xp = 180.0, + hotspots = listOf("loc.poh_hall1_2"), + built = listOf("loc.poh_trophy_head_blank_teak"), + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_mahogany_head_display") { + option( + "Mahogany display", + level = 58, + xp = 280.0, + hotspots = listOf("loc.poh_hall1_2"), + built = listOf("loc.poh_trophy_head_blank_mahogany"), + ) { + material("obj.plank_mahogany", 2) + } + } + row("dbrow.poh_gilded_head_display") { + option( + "Gilded display", + level = 78, + xp = 600.0, + hotspots = listOf("loc.poh_hall1_2"), + built = listOf("loc.poh_trophy_head_blank_gilded"), + ) { + material("obj.plank_mahogany", 2) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_oak_fish_display") { + option( + "Oak display", + level = 36, + xp = 120.0, + hotspots = listOf("loc.poh_hall1_4"), + built = listOf("loc.poh_trophy_fish_blank_oak"), + ) { + material("obj.plank_oak", 2) + } + } + row("dbrow.poh_teak_fish_display") { + option( + "Teak display", + level = 56, + xp = 180.0, + hotspots = listOf("loc.poh_hall1_4"), + built = listOf("loc.poh_trophy_fish_blank_teak"), + ) { + material("obj.plank_teak", 2) + } + } + row("dbrow.poh_mahogany_fish_display") { + option( + "Mahogany display", + level = 76, + xp = 280.0, + hotspots = listOf("loc.poh_hall1_4"), + built = listOf("loc.poh_trophy_fish_blank_mahogany"), + ) { + material("obj.plank_mahogany", 2) + } + } + row("dbrow.poh_mithril_armour") { + option( + "Mithril armour", + level = 28, + xp = 135.0, + hotspots = listOf("loc.poh_hall1_5"), + built = listOf("loc.poh_armour_mithril_5"), + ) { + material("obj.plank_oak", 2) + material("obj.mithril_full_helm", 1) + material("obj.mithril_platebody", 1) + material("obj.mithril_plateskirt", 1) + } + } + row("dbrow.poh_adamantite_armour") { + option( + "Adamantite armour", + level = 28, + xp = 150.0, + hotspots = listOf("loc.poh_hall1_5"), + built = listOf("loc.poh_armour_adamant_5"), + ) { + material("obj.plank_oak", 2) + material("obj.adamant_full_helm", 1) + material("obj.adamant_platebody", 1) + material("obj.adamant_plateskirt", 1) + } + } + row("dbrow.poh_runite_armour") { + option( + "Runite armour", + level = 28, + xp = 165.0, + hotspots = listOf("loc.poh_hall1_5"), + built = listOf("loc.poh_armour_rune_5"), + ) { + material("obj.plank_oak", 2) + material("obj.rune_full_helm", 1) + material("obj.rune_platebody", 1) + material("obj.rune_plateskirt", 1) + } + } + row("dbrow.poh_red_castlewars_armour") { + option( + "Red Castlewars armour", + level = 28, + xp = 135.0, + hotspots = listOf("loc.poh_hall1_6"), + built = listOf("loc.poh_armour_castlewars_red_6"), + ) { + material("obj.plank_oak", 2) + material("obj.castlewars_med_helm", 1) + material("obj.castlewars_armour_body", 1) + material("obj.castlewars_shield", 1) + } + } + row("dbrow.poh_white_castlewars_armour") { + option( + "White Castlewars armour", + level = 28, + xp = 150.0, + hotspots = listOf("loc.poh_hall1_6"), + built = listOf("loc.poh_armour_castlewars_white_6"), + ) { + material("obj.plank_oak", 2) + material("obj.castlewars_med_helm_2", 1) + material("obj.castlewars_armour_body_2", 1) + material("obj.castlewars_shield_2", 1) + } + } + row("dbrow.poh_gold_castlewars_armour") { + option( + "Gold Castlewars armour", + level = 28, + xp = 165.0, + hotspots = listOf("loc.poh_hall1_6"), + built = listOf("loc.poh_armour_castlewars_gold_6"), + ) { + material("obj.plank_oak", 2) + material("obj.castlewars_med_helm_3", 1) + material("obj.castlewars_armour_body_3", 1) + material("obj.castlewars_shield_3", 1) + } + } + row("dbrow.poh_rune_case_1") { + option( + "Rune case 1", + level = 41, + xp = 190.0, + hotspots = listOf("loc.poh_hall1_7"), + built = listOf("loc.poh_display_case_rune1_6"), + ) { + material("obj.plank_teak", 2) + material("obj.molten_glass", 2) + material("obj.airrune", 1) + material("obj.earthrune", 1) + material("obj.firerune", 1) + material("obj.waterrune", 1) + } + } + row("dbrow.poh_rune_case_2") { + option( + "Rune case 2", + level = 41, + xp = 212.0, + hotspots = listOf("loc.poh_hall1_7"), + built = listOf("loc.poh_display_case_rune2_6"), + ) { + material("obj.plank_teak", 2) + material("obj.molten_glass", 2) + material("obj.bodyrune", 1) + material("obj.chaosrune", 1) + material("obj.cosmicrune", 1) + material("obj.naturerune", 1) + } + } + row("dbrow.poh_rune_case_3") { + option( + "Rune case 3", + level = 41, + xp = 247.0, + hotspots = listOf("loc.poh_hall1_7"), + built = listOf("loc.poh_display_case_rune3_6"), + ) { + material("obj.plank_teak", 2) + material("obj.molten_glass", 2) + material("obj.bloodrune", 1) + material("obj.deathrune", 1) + material("obj.lawrune", 1) + material("obj.soulrune", 1) + } + } + row("dbrow.poh_king_arthur_portrait") { + option( + "King Arthur", + level = 35, + xp = 211.0, + hotspots = listOf("loc.poh_hall2_2"), + built = listOf("loc.poh_portrait_kingarthur_1"), + ) { + material("obj.plank_teak", 2) + material("obj.poh_unframed_painting_kingarthur", 1) + } + } + row("dbrow.poh_elena_portrait") { + option( + "Elena", + level = 35, + xp = 211.0, + hotspots = listOf("loc.poh_hall2_2"), + built = listOf("loc.poh_portrait_elena_1"), + ) { + material("obj.plank_teak", 2) + material("obj.poh_unframed_painting_elena", 1) + } + } + row("dbrow.poh_giant_dwarf_portrait") { + option( + "Giant Dwarf", + level = 35, + xp = 211.0, + hotspots = listOf("loc.poh_hall2_2"), + built = listOf("loc.poh_portrait_giantdwarf_1"), + ) { + material("obj.plank_teak", 2) + material("obj.poh_unframed_painting_giantdwarf", 1) + } + } + row("dbrow.poh_miscellanians_portrait") { + option( + "Miscellanians", + level = 55, + xp = 311.0, + hotspots = listOf("loc.poh_hall2_2"), + built = listOf("loc.poh_portrait_prince+princess_1"), + ) { + material("obj.plank_mahogany", 2) + material("obj.poh_unframed_painting_prince+princess", 1) + } + } + row("dbrow.poh_landscape_lumbridge") { + option( + "Lumbridge", + level = 44, + xp = 314.0, + hotspots = listOf("loc.poh_hall2_3"), + built = listOf("loc.poh_landscape_lumbridge_1"), + ) { + material("obj.plank_teak", 3) + material("obj.poh_unframed_painting_lumbridge", 1) + } + } + row("dbrow.poh_landscape_desert") { + option( + "The desert", + level = 44, + xp = 314.0, + hotspots = listOf("loc.poh_hall2_3"), + built = listOf("loc.poh_landscape_desert_1"), + ) { + material("obj.plank_teak", 3) + material("obj.poh_unframed_painting_desert", 1) + } + } + row("dbrow.poh_landscape_morytania") { + option( + "Morytania", + level = 44, + xp = 314.0, + hotspots = listOf("loc.poh_hall2_3"), + built = listOf("loc.poh_landscape_morytania_1"), + ) { + material("obj.plank_teak", 3) + material("obj.poh_unframed_painting_morytania", 1) + } + } + row("dbrow.poh_landscape_karamja") { + option( + "Karamja", + level = 65, + xp = 464.0, + hotspots = listOf("loc.poh_hall2_3"), + built = listOf("loc.poh_landscape_karamja_1"), + ) { + material("obj.plank_mahogany", 3) + material("obj.poh_unframed_painting_karamja", 1) + } + } + row("dbrow.poh_landscape_isafdar") { + option( + "Isafdar", + level = 65, + xp = 464.0, + hotspots = listOf("loc.poh_hall2_3"), + built = listOf("loc.poh_landscape_istafar_1"), + ) { + material("obj.plank_mahogany", 3) + material("obj.poh_unframed_painting_istafar", 1) + } + } + row("dbrow.poh_mounted_anti_dragon_shield") { + option( + "Anti-dragon shield", + level = 47, + xp = 280.0, + hotspots = listOf("loc.poh_hall2_4"), + built = listOf("loc.poh_trophy_antidragonbreath_4"), + ) { + material("obj.plank_teak", 3) + material("obj.antidragonbreathshield", 1) + } + } + row("dbrow.poh_mounted_amulet_of_glory") { + option( + "Amulet of glory", + level = 47, + xp = 290.0, + hotspots = listOf("loc.poh_hall2_4"), + built = listOf("loc.poh_trophy_amuletofglory_4"), + ) { + material("obj.plank_teak", 3) + material("obj.amulet_of_glory", 1) + } + } + row("dbrow.poh_mounted_cape_of_legends") { + option( + "Cape of legends", + level = 47, + xp = 300.0, + hotspots = listOf("loc.poh_hall2_4"), + built = listOf("loc.poh_trophy_legendscape_4"), + ) { + material("obj.plank_teak", 3) + material("obj.cape_of_legends", 1) + } + } + row("dbrow.poh_mounted_mythical_cape") { + option( + "Mythical cape", + level = 47, + xp = 370.0, + hotspots = listOf("loc.poh_hall2_4"), + built = listOf("loc.poh_trophy_mythical_cape"), + ) { + material("obj.plank_teak", 3) + material("obj.mythical_cape", 1) + } + } + row("dbrow.poh_mounted_silverlight") { + option( + "Silverlight", + level = 42, + xp = 187.0, + hotspots = listOf("loc.poh_hall2_5"), + built = listOf("loc.poh_trophy_silverlight_5"), + ) { + material("obj.plank_teak", 2) + material("obj.silverlight", 1) + } + } + row("dbrow.poh_mounted_excalibur") { + option( + "Excalibur", + level = 42, + xp = 194.0, + hotspots = listOf("loc.poh_hall2_5"), + built = listOf("loc.poh_trophy_excalibur_5"), + ) { + material("obj.plank_teak", 2) + material("obj.excalibur", 1) + } + } + row("dbrow.poh_mounted_darklight") { + option( + "Darklight", + level = 42, + xp = 202.0, + hotspots = listOf("loc.poh_hall2_5"), + built = listOf("loc.poh_trophy_darklight_5"), + ) { + material("obj.plank_teak", 2) + material("obj.darklight", 1) + } + } + row("dbrow.poh_small_map") { + option( + "Small map", + level = 38, + xp = 211.0, + hotspots = listOf("loc.poh_hall2_6"), + built = listOf("loc.poh_wall_map_freearea"), + ) { + material("obj.plank_teak", 2) + material("obj.poh_unframed_painting_small_map", 1) + } + } + row("dbrow.poh_medium_map") { + option( + "Medium map", + level = 58, + xp = 451.0, + hotspots = listOf("loc.poh_hall2_6"), + built = listOf("loc.poh_wall_map_world"), + ) { + material("obj.plank_mahogany", 3) + material("obj.poh_unframed_painting_medium_map", 1) + } + } + row("dbrow.poh_large_map") { + option( + "Large map", + level = 78, + xp = 591.0, + hotspots = listOf("loc.poh_hall2_6"), + built = listOf("loc.poh_wall_map_world+underground"), + ) { + material("obj.plank_mahogany", 4) + material("obj.poh_unframed_painting_large_map", 1) + } + } + + /* Garden. */ + // TODO(unmatched): Pumpkin / Beehive (style 1 and 2) - seasonal built locs absent from rev 240. + // TODO(unmatched): Exit portal (Annihilation unlock) - blueprint obj absent from rev 240. + row("dbrow.poh_exit_portal_build") { + option( + "Exit portal", + level = 1, + xp = 100.0, + hotspots = listOf("loc.poh_crude_garden_1", "loc.poh_posh_garden_1"), + built = listOf("loc.poh_exit_portal", "loc.poh_exit_portal"), + ) { + material("obj.iron_bar", 10) + } + } + row("dbrow.poh_garden_greenman_statue") { + option( + "Greenman statue", + level = 1, + xp = 0.0, + hotspots = listOf("loc.poh_crude_garden_1", "loc.poh_posh_garden_1"), + built = listOf("loc.poh_greenman_statue_rot0", "loc.poh_greenman_statue_rot0"), + ) { + material("obj.greenman_statue", 1) + } + } + row("dbrow.poh_decorative_rock") { + option( + "Decorative rock", + level = 5, + xp = 100.0, + hotspots = listOf("loc.poh_crude_garden_1"), + built = listOf("loc.poh_crude_garden_centrepiece2"), + ) { + material("obj.limestonebrick", 5) + } + } + row("dbrow.poh_pond") { + option( + "Pond", + level = 10, + xp = 100.0, + hotspots = listOf("loc.poh_crude_garden_1"), + built = listOf("loc.poh_crude_garden_centrepiece3"), + ) { + material("obj.softclay", 10) + } + } + row("dbrow.poh_imp_statue") { + option( + "Imp statue", + level = 15, + xp = 150.0, + hotspots = listOf("loc.poh_crude_garden_1"), + built = listOf("loc.poh_crude_garden_centrepiece4"), + ) { + material("obj.limestonebrick", 5) + material("obj.softclay", 5) + } + } + row("dbrow.poh_dungeon_entrance") { + option( + "Dungeon entrance", + level = 70, + xp = 500.0, + hotspots = listOf("loc.poh_crude_garden_1", "loc.poh_posh_garden_1"), + built = + listOf( + "loc.poh_crude_garden_centrepiece5", + "loc.poh_crude_garden_centrepiece5", + ), + ) { + material("obj.marble_block", 1) + } + } + row("dbrow.poh_tip_jar") { + option( + "Tip jar", + level = 40, + xp = 651.0, + hotspots = listOf("loc.poh_garden_8"), + built = listOf("loc.poh_tipjar"), + ) { + material("obj.plank_mahogany", 2) + material("obj.molten_glass", 1) + material("obj.gold_leaf", 1) + material("obj.platinum", 5) + } + } + row("dbrow.poh_garden_dead_tree") { + option( + "Tree", + level = 5, + xp = 31.0, + hotspots = listOf("loc.poh_crude_garden_2", "loc.poh_crude_garden_3"), + built = listOf("loc.poh_big_tree1_4", "loc.poh_small_tree1_5"), + ) { + material("obj.poh_sapling_tree_1", 1) + } + } + row("dbrow.poh_garden_nice_tree") { + option( + "Nice tree", + level = 10, + xp = 44.0, + hotspots = listOf("loc.poh_crude_garden_2", "loc.poh_crude_garden_3"), + built = listOf("loc.poh_big_tree2_4", "loc.poh_small_tree2_5"), + ) { + material("obj.poh_sapling_tree_2", 1) + } + } + row("dbrow.poh_garden_oak_tree") { + option( + "Oak tree", + level = 15, + xp = 70.0, + hotspots = listOf("loc.poh_crude_garden_2", "loc.poh_crude_garden_3"), + built = listOf("loc.poh_big_tree3_4", "loc.poh_small_tree3_5"), + ) { + material("obj.poh_sapling_tree_3", 1) + } + } + row("dbrow.poh_garden_willow_tree") { + option( + "Willow tree", + level = 30, + xp = 100.0, + hotspots = listOf("loc.poh_crude_garden_2", "loc.poh_crude_garden_3"), + built = listOf("loc.poh_big_tree4_4", "loc.poh_small_tree4_5"), + ) { + material("obj.poh_sapling_tree_4", 1) + } + } + row("dbrow.poh_garden_maple_tree") { + option( + "Maple tree", + level = 45, + xp = 122.0, + hotspots = listOf("loc.poh_crude_garden_2", "loc.poh_crude_garden_3"), + built = listOf("loc.poh_big_tree5_4", "loc.poh_small_tree5_5"), + ) { + material("obj.poh_sapling_tree_5", 1) + } + } + row("dbrow.poh_garden_yew_tree") { + option( + "Yew tree", + level = 60, + xp = 141.0, + hotspots = listOf("loc.poh_crude_garden_2", "loc.poh_crude_garden_3"), + built = listOf("loc.poh_big_tree6_4", "loc.poh_small_tree6_5"), + ) { + material("obj.poh_sapling_tree_6", 1) + } + } + row("dbrow.poh_garden_magic_tree") { + option( + "Magic tree", + level = 75, + xp = 223.0, + hotspots = listOf("loc.poh_crude_garden_2", "loc.poh_crude_garden_3"), + built = listOf("loc.poh_big_tree7_4", "loc.poh_small_tree7_5"), + ) { + material("obj.poh_sapling_tree_7", 1) + } + } + row("dbrow.poh_garden_fern") { + option( + "Fern", + level = 1, + xp = 31.0, + hotspots = listOf("loc.poh_crude_garden_4"), + built = listOf("loc.poh_plantbig1a"), + ) { + material("obj.poh_sapling_plant_1", 1) + } + } + row("dbrow.poh_garden_bush") { + option( + "Bush", + level = 6, + xp = 70.0, + hotspots = listOf("loc.poh_crude_garden_4"), + built = listOf("loc.poh_plantbig1b"), + ) { + material("obj.poh_sapling_plant_2", 1) + } + } + row("dbrow.poh_garden_tall_plant") { + option( + "Tall plant", + level = 12, + xp = 100.0, + hotspots = listOf("loc.poh_crude_garden_4"), + built = listOf("loc.poh_plantbig1c"), + ) { + material("obj.poh_sapling_plant_3", 1) + } + } + row("dbrow.poh_garden_short_plant") { + option( + "Short plant", + level = 1, + xp = 31.0, + hotspots = listOf("loc.poh_crude_garden_5"), + built = listOf("loc.poh_plantbig2a"), + ) { + material("obj.poh_sapling_plant_1", 1) + } + } + row("dbrow.poh_garden_large_leaf_bush") { + option( + "Large leaf bush", + level = 6, + xp = 70.0, + hotspots = listOf("loc.poh_crude_garden_5"), + built = listOf("loc.poh_plantbig2b"), + ) { + material("obj.poh_sapling_plant_2", 1) + } + } + row("dbrow.poh_garden_huge_plant") { + option( + "Huge plant", + level = 12, + xp = 100.0, + hotspots = listOf("loc.poh_crude_garden_5"), + built = listOf("loc.poh_plantbig2c"), + ) { + material("obj.poh_sapling_plant_3", 1) + } + } + row("dbrow.poh_garden_plant") { + option( + "Plant", + level = 1, + xp = 31.0, + hotspots = listOf("loc.poh_crude_garden_6"), + built = listOf("loc.poh_plantbsmall1a"), + ) { + material("obj.poh_sapling_plant_1", 1) + } + } + row("dbrow.poh_garden_small_fern") { + option( + "Small fern", + level = 6, + xp = 70.0, + hotspots = listOf("loc.poh_crude_garden_6"), + built = listOf("loc.poh_plantbsmall1b"), + ) { + material("obj.poh_sapling_plant_2", 1) + } + } + row("dbrow.poh_garden_fern_small") { + option( + "Fern", + level = 12, + xp = 100.0, + hotspots = listOf("loc.poh_crude_garden_6"), + built = listOf("loc.poh_plantbsmall1c"), + ) { + material("obj.poh_sapling_plant_3", 1) + } + } + row("dbrow.poh_garden_dock_leaf") { + option( + "Dock leaf", + level = 1, + xp = 31.0, + hotspots = listOf("loc.poh_crude_garden_7"), + built = listOf("loc.poh_plantbsmall2a"), + ) { + material("obj.poh_sapling_plant_1", 1) + } + } + row("dbrow.poh_garden_thistle") { + option( + "Thistle", + level = 6, + xp = 70.0, + hotspots = listOf("loc.poh_crude_garden_7"), + built = listOf("loc.poh_plantbsmall2b"), + ) { + material("obj.poh_sapling_plant_2", 1) + } + } + row("dbrow.poh_garden_reeds") { + option( + "Reeds", + level = 12, + xp = 100.0, + hotspots = listOf("loc.poh_crude_garden_7"), + built = listOf("loc.poh_plantbsmall2c"), + ) { + material("obj.poh_sapling_plant_3", 1) + } + } + + /* Formal garden. */ + row("dbrow.poh_gazebo") { + option( + "Gazebo", + level = 65, + xp = 1200.0, + hotspots = listOf("loc.poh_posh_garden_1"), + built = listOf("loc.poh_posh_garden_centrepiece2"), + ) { + material("obj.plank_mahogany", 8) + material("obj.steel_bar", 4) + } + } + row("dbrow.poh_small_fountain") { + option( + "Small fountain", + level = 71, + xp = 500.0, + hotspots = listOf("loc.poh_posh_garden_1"), + built = listOf("loc.poh_posh_garden_centrepiece3"), + ) { + material("obj.marble_block", 1) + } + } + row("dbrow.poh_large_fountain") { + option( + "Large fountain", + level = 75, + xp = 1000.0, + hotspots = listOf("loc.poh_posh_garden_1"), + built = listOf("loc.poh_posh_garden_centrepiece4"), + ) { + material("obj.marble_block", 2) + } + } + row("dbrow.poh_posh_fountain") { + option( + "Posh fountain", + level = 81, + xp = 1500.0, + hotspots = listOf("loc.poh_posh_garden_1"), + built = listOf("loc.poh_posh_garden_centrepiece5"), + ) { + material("obj.marble_block", 3) + } + } + row("dbrow.poh_boundary_stones") { + option( + "Boundary stones", + level = 55, + xp = 100.0, + hotspots = listOf("loc.poh_posh_garden_4"), + built = listOf("loc.poh_fencing1"), + groupBuild = true, + ) { + material("obj.softclay", 10) + } + } + row("dbrow.poh_wooden_fence") { + option( + "Wooden fence", + level = 59, + xp = 280.0, + hotspots = listOf("loc.poh_posh_garden_4"), + built = listOf("loc.poh_fencing2"), + groupBuild = true, + ) { + material("obj.woodplank", 10) + } + } + row("dbrow.poh_stone_wall") { + option( + "Stone wall", + level = 63, + xp = 200.0, + hotspots = listOf("loc.poh_posh_garden_4"), + built = listOf("loc.poh_fencing3"), + groupBuild = true, + ) { + material("obj.limestonebrick", 10) + } + } + row("dbrow.poh_iron_railings") { + option( + "Iron railings", + level = 67, + xp = 220.0, + hotspots = listOf("loc.poh_posh_garden_4"), + built = listOf("loc.poh_fencing4"), + groupBuild = true, + ) { + material("obj.iron_bar", 10) + material("obj.limestonebrick", 6) + } + } + row("dbrow.poh_picket_fence") { + option( + "Picket fence", + level = 71, + xp = 640.0, + hotspots = listOf("loc.poh_posh_garden_4"), + built = listOf("loc.poh_fencing5"), + groupBuild = true, + ) { + material("obj.plank_oak", 10) + material("obj.steel_bar", 2) + } + } + row("dbrow.poh_garden_fence") { + option( + "Garden fence", + level = 75, + xp = 940.0, + hotspots = listOf("loc.poh_posh_garden_4"), + built = listOf("loc.poh_fencing6"), + groupBuild = true, + ) { + material("obj.plank_teak", 10) + material("obj.steel_bar", 2) + } + } + row("dbrow.poh_marble_wall") { + option( + "Marble wall", + level = 79, + xp = 4000.0, + hotspots = listOf("loc.poh_posh_garden_4"), + built = listOf("loc.poh_fencing7"), + groupBuild = true, + ) { + material("obj.marble_block", 8) + } + } + row("dbrow.poh_thorny_hedge") { + option( + "Thorny hedge", + level = 56, + xp = 70.0, + hotspots = + listOf( + "loc.poh_posh_garden_5cor", + "loc.poh_posh_garden_5end", + "loc.poh_posh_garden_5mid", + ), + built = + listOf( + "loc.poh_hedgecorner1", + "loc.poh_hedgeend1", + "loc.poh_hedgemiddle1", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_hedge1", 1) + } + } + row("dbrow.poh_nice_hedge") { + option( + "Nice hedge", + level = 60, + xp = 100.0, + hotspots = + listOf( + "loc.poh_posh_garden_5cor", + "loc.poh_posh_garden_5end", + "loc.poh_posh_garden_5mid", + ), + built = + listOf( + "loc.poh_hedgecorner2", + "loc.poh_hedgeend2", + "loc.poh_hedgemiddle2", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_hedge2", 1) + } + } + row("dbrow.poh_small_box_hedge") { + option( + "Small box hedge", + level = 64, + xp = 122.0, + hotspots = + listOf( + "loc.poh_posh_garden_5cor", + "loc.poh_posh_garden_5end", + "loc.poh_posh_garden_5mid", + ), + built = + listOf( + "loc.poh_hedgecorner3", + "loc.poh_hedgeend3", + "loc.poh_hedgemiddle3", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_hedge3", 1) + } + } + row("dbrow.poh_topiary_hedge") { + option( + "Topiary hedge", + level = 68, + xp = 141.0, + hotspots = + listOf( + "loc.poh_posh_garden_5cor", + "loc.poh_posh_garden_5end", + "loc.poh_posh_garden_5mid", + ), + built = + listOf( + "loc.poh_hedgecorner4", + "loc.poh_hedgeend4", + "loc.poh_hedgemiddle4", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_hedge4", 1) + } + } + row("dbrow.poh_fancy_hedge") { + option( + "Fancy hedge", + level = 72, + xp = 158.0, + hotspots = + listOf( + "loc.poh_posh_garden_5cor", + "loc.poh_posh_garden_5end", + "loc.poh_posh_garden_5mid", + ), + built = + listOf( + "loc.poh_hedgecorner5", + "loc.poh_hedgeend5", + "loc.poh_hedgemiddle5", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_hedge5", 1) + } + } + row("dbrow.poh_tall_fancy_hedge") { + option( + "Tall fancy hedge", + level = 76, + xp = 223.0, + hotspots = + listOf( + "loc.poh_posh_garden_5cor", + "loc.poh_posh_garden_5end", + "loc.poh_posh_garden_5mid", + ), + built = + listOf( + "loc.poh_hedgecorner6", + "loc.poh_hedgeend6", + "loc.poh_hedgemiddle6", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_hedge6", 1) + } + } + row("dbrow.poh_tall_box_hedge") { + option( + "Tall box hedge", + level = 80, + xp = 316.0, + hotspots = + listOf( + "loc.poh_posh_garden_5cor", + "loc.poh_posh_garden_5end", + "loc.poh_posh_garden_5mid", + ), + built = + listOf( + "loc.poh_hedgecorner7", + "loc.poh_hedgeend7", + "loc.poh_hedgemiddle7", + ), + groupBuild = true, + ) { + material("obj.poh_sapling_hedge7", 1) + } + } + row("dbrow.poh_sunflower") { + option( + "Sunflower", + level = 66, + xp = 70.0, + hotspots = listOf("loc.poh_posh_garden_2", "loc.poh_posh_garden_6"), + built = listOf("loc.poh_flowera1_big", "loc.poh_flowera1_small"), + ) { + material("obj.poh_sapling_flowera1", 1) + } + } + row("dbrow.poh_marigolds") { + option( + "Marigolds", + level = 71, + xp = 100.0, + hotspots = listOf("loc.poh_posh_garden_2", "loc.poh_posh_garden_6"), + built = listOf("loc.poh_flowera2_big", "loc.poh_flowera2_small"), + ) { + material("obj.poh_sapling_flowera2", 1) + } + } + row("dbrow.poh_roses") { + option( + "Roses", + level = 76, + xp = 122.0, + hotspots = listOf("loc.poh_posh_garden_2", "loc.poh_posh_garden_6"), + built = listOf("loc.poh_flowera3_big", "loc.poh_flowera3_small"), + ) { + material("obj.poh_sapling_flowera3", 1) + } + } + row("dbrow.poh_rosemary") { + option( + "Rosemary", + level = 66, + xp = 70.0, + hotspots = listOf("loc.poh_posh_garden_3", "loc.poh_posh_garden_7"), + built = listOf("loc.poh_flowerb1_big", "loc.poh_flowerb1_small"), + ) { + material("obj.poh_sapling_flowerb1", 1) + } + } + row("dbrow.poh_daffodils") { + option( + "Daffodils", + level = 71, + xp = 100.0, + hotspots = listOf("loc.poh_posh_garden_3", "loc.poh_posh_garden_7"), + built = listOf("loc.poh_flowerb2_big", "loc.poh_flowerb2_small"), + ) { + material("obj.poh_sapling_flowerb2", 1) + } + } + row("dbrow.poh_bluebells") { + option( + "Bluebells", + level = 76, + xp = 122.0, + hotspots = listOf("loc.poh_posh_garden_3", "loc.poh_posh_garden_7"), + built = listOf("loc.poh_flowerb3_big", "loc.poh_flowerb3_small"), + ) { + material("obj.poh_sapling_flowerb3", 1) + } + } + + /* Superior garden - pools upgrade the previous tier. */ + row("dbrow.poh_restoration_pool") { + option( + "Restoration pool", + level = 65, + xp = 706.0, + hotspots = listOf("loc.poh_superior_garden_hotspot_pool"), + built = listOf("loc.poh_pool_restoration"), + ) { + material("obj.limestonebrick", 5) + material("obj.bucket_water", 5) + material("obj.soulrune", 1000) + material("obj.bodyrune", 1000) + } + } + row("dbrow.poh_revitalisation_pool") { + option( + "Revitalisation pool", + level = 70, + xp = 850.0, + hotspots = listOf("loc.poh_pool_restoration"), + built = listOf("loc.poh_pool_revitalisation"), + ) { + material("obj.4dosestamina", 10) + } + } + row("dbrow.poh_rejuvenation_pool") { + option( + "Rejuvenation pool", + level = 80, + xp = 900.0, + hotspots = listOf("loc.poh_pool_revitalisation"), + built = listOf("loc.poh_pool_rejuvenation"), + ) { + material("obj.4doseprayerrestore", 10) + } + } + row("dbrow.poh_fancy_rejuvenation_pool") { + option( + "Fancy rejuvenation pool", + level = 85, + xp = 1950.0, + hotspots = listOf("loc.poh_pool_rejuvenation"), + built = listOf("loc.poh_pool_recovery"), + ) { + material("obj.4dose2restore", 10) + material("obj.marble_block", 2) + } + } + row("dbrow.poh_ornate_rejuvenation_pool") { + option( + "Ornate rejuvenation pool", + level = 90, + xp = 3107.0, + hotspots = listOf("loc.poh_pool_recovery"), + built = listOf("loc.poh_pool_regeneration"), + ) { + material("obj.antivenom4", 10) + material("obj.gold_leaf", 5) + material("obj.bloodrune", 1000) + } + } + row("dbrow.poh_spirit_tree_teleport") { + option( + "Spirit tree", + level = 75, + xp = 350.0, + hotspots = listOf("loc.poh_superior_garden_hotspot_treering"), + built = listOf("loc.poh_spirit_tree"), + ) { + material("obj.plantpot_spirit_tree_sapling", 1) + } + } + row("dbrow.poh_obelisk") { + option( + "Obelisk", + level = 80, + xp = 3000.0, + hotspots = listOf("loc.poh_superior_garden_hotspot_treering"), + built = listOf("loc.poh_wilderness_obelisk"), + ) { + material("obj.wild_cave_obelisk_crystal", 4) + material("obj.marble_block", 4) + } + } + row("dbrow.poh_fairy_ring_teleport") { + option( + "Fairy ring", + level = 85, + xp = 535.0, + hotspots = listOf("loc.poh_superior_garden_hotspot_treering"), + built = listOf("loc.poh_fairy_ring"), + ) { + material("obj.mortmyremushroom", 10) + material("obj.poh_fairy_enchantment", 1) + } + } + row("dbrow.poh_spiritual_fairy_tree") { + option( + "Spirit tree & fairy ring", + level = 95, + xp = 885.0, + hotspots = listOf("loc.poh_superior_garden_hotspot_treering"), + built = listOf("loc.poh_spirit_ring"), + ) { + material("obj.plantpot_spirit_tree_sapling", 1) + material("obj.mortmyremushroom", 10) + material("obj.poh_fairy_enchantment", 1) + } + } + row("dbrow.poh_zen_theme") { + option( + "Zen theme", + level = 65, + xp = 474.0, + hotspots = listOf("loc.poh_superior_garden_hotspot_theme_feature"), + built = listOf("loc.poh_theme_zen_hero"), + groupBuild = true, + ) { + material("obj.bucket_sand", 6) + material("obj.handsand_pink_dye", 1) + material("obj.poh_sapling_tree_2", 1) + } + } + row("dbrow.poh_otherworldly_theme") { + option( + "Otherworldly theme", + level = 75, + xp = 316.0, + hotspots = listOf("loc.poh_superior_garden_hotspot_theme_feature"), + built = listOf("loc.poh_theme_zanaris_hero"), + groupBuild = true, + ) { + material("obj.bucket_supercompost", 8) + material("obj.bluedye", 1) + material("obj.mortmyremushroom", 4) + material("obj.fairy_enchanted_secateurs", 1) + } + } + row("dbrow.poh_volcanic_theme") { + option( + "Volcanic theme", + level = 85, + xp = 4464.0, + hotspots = listOf("loc.poh_superior_garden_hotspot_theme_feature"), + built = listOf("loc.poh_theme_tzhaar_hero"), + groupBuild = true, + ) { + material("obj.enakh_granite_medium", 2) + material("obj.onyx", 6) + material("obj.firerune", 1000) + material("obj.lavarune", 2000) + } + } + row("dbrow.poh_topiary_bush") { + option( + "Topiary bush", + level = 65, + xp = 141.0, + hotspots = listOf("loc.poh_superior_garden_hotspot_topiary"), + built = listOf("loc.poh_topiary_null"), + ) { + material("obj.poh_superior_garden_topiary", 1) + } + } + row("dbrow.poh_redwood_fence") { + option( + "Redwood fence", + level = 75, + xp = 240.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_fence_middle", + "loc.poh_superior_garden_hotspot_fence_post", + "loc.poh_superior_garden_hotspot_fence_post_m", + ), + built = + listOf( + "loc.poh_redwood_fence_middle", + "loc.poh_redwood_fence_post", + "loc.poh_redwood_fence_post_m", + ), + groupBuild = true, + ) { + material("obj.redwood_logs", 10) + material("obj.steel_bar", 2) + } + } + row("dbrow.poh_superior_marble_wall") { + option( + "Marble wall", + level = 79, + xp = 4000.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_fence_middle", + "loc.poh_superior_garden_hotspot_fence_post", + "loc.poh_superior_garden_hotspot_fence_post_m", + ), + built = listOf("loc.poh_fencing7", "loc.poh_fencing7", "loc.poh_fencing7"), + groupBuild = true, + ) { + material("obj.marble_block", 8) + } + } + row("dbrow.poh_obsidian_fence") { + option( + "Obsidian fence", + level = 83, + xp = 2741.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_fence_middle", + "loc.poh_superior_garden_hotspot_fence_post", + "loc.poh_superior_garden_hotspot_fence_post_m", + ), + built = + listOf( + "loc.poh_obsidian_fence_middle", + "loc.poh_obsidian_fence_post", + "loc.poh_obsidian_fence_post_m", + ), + groupBuild = true, + ) { + material("obj.tzhaar_staff", 10) + material("obj.tzhaar_maul", 2) + material("obj.tzhaar_throwingring", 25) + } + } + row("dbrow.poh_teak_garden_bench") { + option( + "Teak garden bench", + level = 66, + xp = 540.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_seating_a_left", + "loc.poh_superior_garden_hotspot_seating_a_right", + ), + built = + listOf( + "loc.poh_garden_bench_teak_left", + "loc.poh_garden_bench_teak_right", + ), + groupBuild = true, + ) { + material("obj.plank_teak", 6) + } + } + row("dbrow.poh_teak_garden_bench_2") { + option( + "Teak garden bench", + level = 66, + xp = 540.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_seating_b_left", + "loc.poh_superior_garden_hotspot_seating_b_right", + ), + built = + listOf( + "loc.poh_garden_bench_teak_left", + "loc.poh_garden_bench_teak_right", + ), + groupBuild = true, + ) { + material("obj.plank_teak", 6) + } + } + row("dbrow.poh_gnome_bench") { + option( + "Gnome bench", + level = 77, + xp = 840.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_seating_a_left", + "loc.poh_superior_garden_hotspot_seating_a_right", + ), + built = + listOf( + "loc.poh_garden_bench_gnome_left", + "loc.poh_garden_bench_gnome_right", + ), + groupBuild = true, + ) { + material("obj.plank_mahogany", 6) + } + } + row("dbrow.poh_gnome_bench_2") { + option( + "Gnome bench", + level = 77, + xp = 840.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_seating_b_left", + "loc.poh_superior_garden_hotspot_seating_b_right", + ), + built = + listOf( + "loc.poh_garden_bench_gnome_left", + "loc.poh_garden_bench_gnome_right", + ), + groupBuild = true, + ) { + material("obj.plank_mahogany", 6) + } + } + row("dbrow.poh_marble_bench") { + option( + "Marble decorative bench", + level = 88, + xp = 3000.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_seating_a_left", + "loc.poh_superior_garden_hotspot_seating_a_right", + ), + built = + listOf( + "loc.poh_garden_bench_marble_left", + "loc.poh_garden_bench_marble_right", + ), + groupBuild = true, + ) { + material("obj.marble_block", 6) + } + } + row("dbrow.poh_marble_bench_2") { + option( + "Marble decorative bench", + level = 88, + xp = 3000.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_seating_b_left", + "loc.poh_superior_garden_hotspot_seating_b_right", + ), + built = + listOf( + "loc.poh_garden_bench_marble_left", + "loc.poh_garden_bench_marble_right", + ), + groupBuild = true, + ) { + material("obj.marble_block", 6) + } + } + row("dbrow.poh_obsidian_bench") { + option( + "Obsidian decorative bench", + level = 98, + xp = 2331.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_seating_a_left", + "loc.poh_superior_garden_hotspot_seating_a_right", + ), + built = + listOf( + "loc.poh_garden_bench_obsidian_left", + "loc.poh_garden_bench_obsidian_right", + ), + groupBuild = true, + ) { + material("obj.marble_block", 3) + material("obj.onyx", 1) + material("obj.firerune", 250) + material("obj.lavarune", 500) + } + } + row("dbrow.poh_obsidian_bench_2") { + option( + "Obsidian decorative bench", + level = 98, + xp = 2331.0, + hotspots = + listOf( + "loc.poh_superior_garden_hotspot_seating_b_left", + "loc.poh_superior_garden_hotspot_seating_b_right", + ), + built = + listOf( + "loc.poh_garden_bench_obsidian_left", + "loc.poh_garden_bench_obsidian_right", + ), + groupBuild = true, + ) { + material("obj.marble_block", 3) + material("obj.onyx", 1) + material("obj.firerune", 250) + material("obj.lavarune", 500) + } + } + + /* Achievement gallery - the occult altar upgrades whichever spellbook altar is built. */ + row("dbrow.poh_ancient_altar") { + option( + "Ancient altar", + level = 80, + xp = 1490.0, + hotspots = listOf("loc.poh_achievement_hotspot_altar"), + built = listOf("loc.poh_altar_ancient"), + ) { + material("obj.limestonebrick", 10) + material("obj.poh_magic_crystal", 1) + material("obj.poh_ancient_signet", 1) + material("obj.pharaohs_sceptre", 1) + } + } + row("dbrow.poh_lunar_altar") { + option( + "Lunar altar", + level = 80, + xp = 1957.0, + hotspots = listOf("loc.poh_achievement_hotspot_altar"), + built = listOf("loc.poh_altar_lunar"), + ) { + material("obj.limestonebrick", 10) + material("obj.poh_magic_crystal", 1) + material("obj.poh_lunar_signet", 1) + material("obj.astralrune", 10000) + } + } + row("dbrow.poh_dark_altar") { + option( + "Dark altar", + level = 80, + xp = 3888.0, + hotspots = listOf("loc.poh_achievement_hotspot_altar"), + built = listOf("loc.poh_altar_dark"), + ) { + material("obj.limestonebrick", 10) + material("obj.poh_magic_crystal", 1) + material("obj.poh_arceuus_signet", 1) + material("obj.bloodrune", 5000) + material("obj.soulrune", 5000) + } + } + row("dbrow.poh_occult_altar") { + option( + "Occult altar", + level = 90, + xp = 3445.0, + hotspots = listOf("loc.poh_altar_ancient"), + built = listOf("loc.poh_altar_occult_standard"), + ) { + material("obj.poh_lunar_signet", 1) + material("obj.astralrune", 10000) + material("obj.poh_arceuus_signet", 1) + material("obj.bloodrune", 5000) + material("obj.soulrune", 5000) + } + } + row("dbrow.poh_occult_altar_2") { + option( + "Occult altar", + level = 90, + xp = 3445.0, + hotspots = listOf("loc.poh_altar_lunar"), + built = listOf("loc.poh_altar_occult_standard"), + ) { + material("obj.poh_ancient_signet", 1) + material("obj.pharaohs_sceptre", 1) + material("obj.poh_arceuus_signet", 1) + material("obj.bloodrune", 5000) + material("obj.soulrune", 5000) + } + } + row("dbrow.poh_occult_altar_3") { + option( + "Occult altar", + level = 90, + xp = 3445.0, + hotspots = listOf("loc.poh_altar_dark"), + built = listOf("loc.poh_altar_occult_standard"), + ) { + material("obj.poh_ancient_signet", 1) + material("obj.pharaohs_sceptre", 1) + material("obj.poh_lunar_signet", 1) + material("obj.astralrune", 10000) + } + } + row("dbrow.poh_mahogany_adventure_log") { + option( + "Mahogany adventure log", + level = 83, + xp = 504.0, + hotspots = listOf("loc.poh_achievement_hotspot_log"), + built = listOf("loc.poh_adventure_log_1"), + ) { + material("obj.plank_mahogany", 3) + material("obj.papyrus", 2) + material("obj.slayer_gem", 1) + } + } + row("dbrow.poh_gilded_adventure_log") { + option( + "Gilded adventure log", + level = 88, + xp = 1100.0, + hotspots = listOf("loc.poh_achievement_hotspot_log"), + built = listOf("loc.poh_adventure_log_2"), + ) { + material("obj.plank_mahogany", 3) + material("obj.gold_leaf", 2) + material("obj.slayer_gem", 1) + } + } + row("dbrow.poh_marble_adventure_log") { + option( + "Marble adventure log", + level = 93, + xp = 1160.0, + hotspots = listOf("loc.poh_achievement_hotspot_log"), + built = listOf("loc.poh_adventure_log_3"), + ) { + material("obj.marble_block", 2) + material("obj.limestonebrick", 4) + material("obj.slayer_gem", 1) + } + } + row("dbrow.poh_boss_lair_display") { + option( + "Boss lair display", + level = 87, + xp = 1483.0, + hotspots = listOf("loc.poh_achievement_hotspot_lair"), + built = listOf("loc.poh_display_blank"), + ) { + material("obj.steel_bar", 4) + material("obj.molten_glass", 5) + material("obj.plank_mahogany", 10) + } + } + row("dbrow.poh_mounted_emblem") { + option( + "Mounted emblem", + level = 80, + xp = 5300.0, + hotspots = listOf("loc.poh_achievement_hotspot_display"), + built = listOf("loc.poh_mounted_emblem"), + ) { + material("obj.marble_block", 1) + material("obj.gold_leaf", 1) + material("obj.bh_decorative_emblem_10", 1) + } + } + row("dbrow.poh_mounted_coins") { + option( + "Mounted coins", + level = 80, + xp = 800.0, + hotspots = listOf("loc.poh_achievement_hotspot_display"), + built = listOf("loc.poh_mounted_coins"), + ) { + material("obj.marble_block", 1) + material("obj.gold_leaf", 1) + material("obj.coins", 100000000) + } + } + row("dbrow.poh_cape_hanger") { + option( + "Cape hanger", + level = 80, + xp = 800.0, + hotspots = listOf("loc.poh_achievement_hotspot_display"), + built = listOf("loc.poh_mounted_capestand_blank"), + ) { + material("obj.marble_block", 1) + material("obj.gold_leaf", 1) + } + } + row("dbrow.poh_basic_jewellery_box") { + option( + "Basic jewellery box", + level = 81, + xp = 605.0, + hotspots = listOf("loc.poh_achievement_hotspot_jewellerybox"), + built = listOf("loc.poh_jewellery_box_1_base"), + ) { + material("obj.cloth", 1) + material("obj.steel_bar", 1) + material("obj.necklace_of_minigames_8", 3) + material("obj.ring_of_dueling_8", 3) + } + } + row("dbrow.poh_fancy_jewellery_box") { + option( + "Fancy jewellery box", + level = 86, + xp = 1350.0, + hotspots = listOf("loc.poh_jewellery_box_1_base"), + built = listOf("loc.poh_jewellery_box_2_base"), + ) { + material("obj.gold_leaf", 1) + material("obj.jewl_necklace_of_skills_4", 5) + material("obj.jewl_bracelet_of_combat_4", 5) + } + } + row("dbrow.poh_ornate_jewellery_box") { + option( + "Ornate jewellery box", + level = 91, + xp = 2680.0, + hotspots = listOf("loc.poh_jewellery_box_2_base"), + built = listOf("loc.poh_jewellery_box_3_base"), + ) { + material("obj.gold_leaf", 2) + material("obj.amulet_of_glory_4", 8) + material("obj.ring_of_wealth_5", 8) + } + } + row("dbrow.poh_quest_list") { + option( + "Quest list", + level = 80, + xp = 310.0, + hotspots = listOf("loc.poh_achievement_hotspot_questlist"), + built = listOf("loc.poh_quest_list"), + ) { + material("obj.papyrus", 10) + material("obj.gold_leaf", 1) + } + } + + /* Portal nexus - gilded/crystalline tiers upgrade the built nexus. */ + row("dbrow.poh_marble_portal_nexus") { + option( + "Marble portal nexus", + level = 72, + xp = 2000.0, + hotspots = listOf("loc.poh_telenexus_1"), + built = listOf("loc.poh_nexus_portal_1"), + ) { + material("obj.marble_block", 4) + } + } + row("dbrow.poh_gilded_portal_nexus") { + option( + "Gilded portal nexus", + level = 82, + xp = 2600.0, + hotspots = listOf("loc.poh_nexus_portal_1"), + built = listOf("loc.poh_nexus_portal_2"), + ) { + material("obj.marble_block", 4) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_crystalline_portal_nexus") { + option( + "Crystalline portal nexus", + level = 92, + xp = 2600.0, + hotspots = listOf("loc.poh_nexus_portal_2"), + built = listOf("loc.poh_nexus_portal_3"), + ) { + material("obj.poh_magic_crystal", 2) + material("obj.gold_leaf", 2) + } + } + row("dbrow.poh_mounted_xerics_talisman") { + option( + "Mounted xeric's talisman", + level = 72, + xp = 500.0, + hotspots = listOf("loc.poh_nexus_4_amulet", "loc.poh_nexus_5_amulet"), + built = listOf("loc.poh_amulet_xeric", "loc.poh_amulet_xeric"), + ) { + material("obj.plank_mahogany", 1) + material("obj.gold_leaf", 1) + material("obj.xeric_talisman_empty", 1) + material("obj.lizardman_fang", 5000) + } + } + row("dbrow.poh_mounted_digsite_pendant") { + option( + "Mounted digsite pendant", + level = 82, + xp = 800.0, + hotspots = listOf("loc.poh_nexus_4_amulet", "loc.poh_nexus_5_amulet"), + built = listOf("loc.poh_amulet_digsite", "loc.poh_amulet_digsite"), + ) { + material("obj.plank_mahogany", 1) + material("obj.gold_leaf", 1) + material("obj.poh_curator_medallion", 1) + } + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e674e6c43..e7788cbf9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,6 +33,7 @@ include( "server", "or-cache", "tools:osrs-mcp", + "tools:poh-datagen", "tools:wiki-dumping", ) diff --git a/tools/poh-datagen/build.gradle.kts b/tools/poh-datagen/build.gradle.kts new file mode 100644 index 000000000..a77405592 --- /dev/null +++ b/tools/poh-datagen/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("base-conventions") + application +} + +application { + mainClass.set("org.rsmod.tools.pohdatagen.MainKt") +} + +tasks.named("run") { + group = "cache" + description = + "Regenerates api/poh/src/main/resources/poh_rooms.json, poh_hotspots.json and " + + "poh_index_manifest.json from the rev-240 cache. Run manually after cache updates." + workingDir = rootProject.projectDir +} + +dependencies { + implementation("com.google.code.gson:gson:2.13.2") + implementation(libs.fastutil) +} diff --git a/tools/poh-datagen/src/main/kotlin/org/rsmod/tools/pohdatagen/Main.kt b/tools/poh-datagen/src/main/kotlin/org/rsmod/tools/pohdatagen/Main.kt new file mode 100644 index 000000000..0c5ba8618 --- /dev/null +++ b/tools/poh-datagen/src/main/kotlin/org/rsmod/tools/pohdatagen/Main.kt @@ -0,0 +1,491 @@ +package org.rsmod.tools.pohdatagen + +import com.google.gson.GsonBuilder +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import dev.openrune.ServerCacheManager +import dev.openrune.cache.MAPS +import dev.openrune.filesystem.Cache +import dev.openrune.gamevals.GameValProvider +import dev.openrune.map.loc.MapLocDefinition +import dev.openrune.map.loc.MapLocListDecoder +import dev.openrune.map.util.InlineByteBuf +import java.io.File +import java.nio.file.Paths +import java.security.MessageDigest + +/** + * Regenerates the POH room/hotspot resources consumed by `api/poh` from the rev-240 cache. + * + * Reads the room-template map squares out of `.data/cache/LIVE` (the squares silo's static map is + * packed from) and the loc configs out of the packed `SERVER` cache (the types the server resolves + * at runtime), then writes: + * - `api/poh/src/main/resources/poh_rooms.json` + * - `api/poh/src/main/resources/poh_hotspots.json` + * - `api/poh/src/main/resources/poh_index_manifest.json` + * + * Room and hotspot indices are persisted in `character_attrs` blobs, so a regeneration must never + * reorder existing entries: when a committed manifest exists, this tool fails if any previously + * assigned index would change identity. Run manually via `./gradlew :tools:poh-datagen:run`. + */ + +private data class Style(val index: Int, val key: String, val region: Int, val level: Int) + +/** Style slots verified from `poh_hotspot_doorl_